spark-server/internal/transport/tcp/protocol/frame.go
2026-09-18 14:50:41 +08:00

111 lines
3.0 KiB
Go

// Package protocol implements TLS-payload framing and PROXY v1/v2 parsing.
package protocol
import (
"encoding/binary"
"spark-server/internal/pkg/errs"
)
const (
Magic uint16 = 0x5350 // "SP"
HeaderSize = 23
FlagCompress = 1 << 0
CmdAuth uint16 = 1
CmdHeartbeat uint16 = 2
CmdGenerateMsgID uint16 = 3
CmdSendMessage uint16 = 4
CmdSendAck uint16 = 5
CmdMessage uint16 = 6
CmdPushAck uint16 = 7
CmdReadMessage uint16 = 8
CmdRecallMessage uint16 = 9
CmdSyncInbox uint16 = 10
CmdSyncInboxFinish uint16 = 11
CmdSyncMessages uint16 = 12
CmdSyncMessagesFinish uint16 = 13
CmdKick uint16 = 14
CmdDeleteMessage uint16 = 15
)
// Frame is the application header after TLS. SequenceID matches request/response and PUSH_ACK only.
type Frame struct {
Magic uint16
Version uint8
Command uint16
Flags uint16
RequestID uint64
SequenceID uint32
Body []byte
}
func Encode(f Frame) ([]byte, error) {
if len(f.Body) > 0xFFFFFFFF {
return nil, errs.ErrFrameTooLarge
}
buf := make([]byte, HeaderSize+len(f.Body))
binary.BigEndian.PutUint16(buf[0:2], Magic)
buf[2] = f.Version
binary.BigEndian.PutUint16(buf[3:5], f.Command)
binary.BigEndian.PutUint16(buf[5:7], f.Flags)
binary.BigEndian.PutUint64(buf[7:15], f.RequestID)
binary.BigEndian.PutUint32(buf[15:19], f.SequenceID)
binary.BigEndian.PutUint32(buf[19:23], uint32(len(f.Body)))
copy(buf[23:], f.Body)
return buf, nil
}
func Decode(raw []byte, maxBody int) (Frame, error) {
if len(raw) < HeaderSize {
return Frame{}, errs.ErrInvalidFrame
}
magic := binary.BigEndian.Uint16(raw[0:2])
if magic != Magic {
return Frame{}, errs.ErrInvalidFrame
}
bodyLen := int(binary.BigEndian.Uint32(raw[19:23]))
if bodyLen > maxBody || HeaderSize+bodyLen != len(raw) {
return Frame{}, errs.ErrFrameTooLarge
}
return Frame{
Magic: magic,
Version: raw[2],
Command: binary.BigEndian.Uint16(raw[3:5]),
Flags: binary.BigEndian.Uint16(raw[5:7]),
RequestID: binary.BigEndian.Uint64(raw[7:15]),
SequenceID: binary.BigEndian.Uint32(raw[15:19]),
Body: append([]byte(nil), raw[23:]...),
}, nil
}
// LooksLikeFrame reports whether buf starts with Magic.
func LooksLikeFrame(buf []byte) bool {
return len(buf) >= 2 && binary.BigEndian.Uint16(buf[0:2]) == Magic
}
// MaybeFramePrefix is true when buf is a prefix of Magic (including a full Frame).
func MaybeFramePrefix(buf []byte) bool {
if len(buf) == 0 {
return true
}
if buf[0] != byte(Magic>>8) {
return false
}
if len(buf) == 1 {
return true
}
return binary.BigEndian.Uint16(buf[0:2]) == Magic
}
// PeekBodyLength reads BodyLength from a complete header without copying the body.
func PeekBodyLength(header []byte) (int, error) {
if len(header) < HeaderSize {
return 0, errs.ErrInvalidFrame
}
if binary.BigEndian.Uint16(header[0:2]) != Magic {
return 0, errs.ErrInvalidFrame
}
return int(binary.BigEndian.Uint32(header[19:23])), nil
}