111 lines
3.0 KiB
Go
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
|
|
}
|