package gateway import ( "encoding/binary" "errors" "fmt" ) const ( frameHeaderSize = 21 maxFrameSize = 1 << 16 maxFragmentCount = 16 ) const ( ChannelControl = byte(1) ChannelAck = byte(2) ChannelText = byte(3) ChannelVideo = byte(10) ChannelAudio = byte(11) ChannelInput = byte(12) ) var ( ErrFrameTruncated = errors.New("gateway frame truncated") ErrFrameMagic = errors.New("gateway frame magic mismatch") ErrFrameVersion = errors.New("gateway frame version unsupported") ErrFrameChannel = errors.New("gateway frame channel unsupported") ErrFrameFlags = errors.New("gateway frame flags unsupported") ErrFrameFragment = errors.New("gateway frame fragment invalid") ErrFrameLength = errors.New("gateway frame length mismatch") ErrFramePayloadLimit = errors.New("gateway frame payload exceeds channel limit") ErrFrameSize = errors.New("gateway frame exceeds size limit") ErrFrameFragmentedLimit = errors.New("gateway payload requires too many fragments") ) type Frame struct { Channel byte Flags byte Sequence uint32 TimestampMS uint64 FragmentIndex byte FragmentCount byte Payload []byte } func channelLimit(channel byte) (int, bool) { switch channel { case ChannelControl: return 1024, true case ChannelAck: return 2048, true case ChannelText: return 65515, true case ChannelVideo, ChannelAudio, ChannelInput: return 1179, true default: return 0, false } } func EncodeFrame(frame Frame) ([]byte, error) { limit, ok := channelLimit(frame.Channel) if !ok { return nil, ErrFrameChannel } if frame.Flags != 0 { return nil, ErrFrameFlags } if frame.FragmentCount == 0 || frame.FragmentCount > maxFragmentCount || frame.FragmentIndex >= frame.FragmentCount { return nil, ErrFrameFragment } if len(frame.Payload) > limit { return nil, ErrFramePayloadLimit } if len(frame.Payload) > maxFrameSize-frameHeaderSize { return nil, ErrFrameSize } encoded := make([]byte, frameHeaderSize+len(frame.Payload)) encoded[0], encoded[1], encoded[2], encoded[3], encoded[4] = 'V', 'D', 1, frame.Channel, frame.Flags binary.BigEndian.PutUint32(encoded[5:9], frame.Sequence) binary.BigEndian.PutUint64(encoded[9:17], frame.TimestampMS) encoded[17], encoded[18] = frame.FragmentIndex, frame.FragmentCount binary.BigEndian.PutUint16(encoded[19:21], uint16(len(frame.Payload))) copy(encoded[frameHeaderSize:], frame.Payload) return encoded, nil } func DecodeFrame(raw []byte) (Frame, error) { if len(raw) < frameHeaderSize { return Frame{}, ErrFrameTruncated } if len(raw) > maxFrameSize { return Frame{}, ErrFrameSize } if raw[0] != 'V' || raw[1] != 'D' { return Frame{}, ErrFrameMagic } if raw[2] != 1 { return Frame{}, ErrFrameVersion } limit, ok := channelLimit(raw[3]) if !ok { return Frame{}, ErrFrameChannel } if raw[4] != 0 { return Frame{}, ErrFrameFlags } if raw[18] == 0 || raw[18] > maxFragmentCount || raw[17] >= raw[18] { return Frame{}, ErrFrameFragment } payloadLength := int(binary.BigEndian.Uint16(raw[19:21])) if payloadLength > limit { return Frame{}, ErrFramePayloadLimit } if len(raw) != frameHeaderSize+payloadLength { return Frame{}, ErrFrameLength } return Frame{ Channel: raw[3], Flags: raw[4], Sequence: binary.BigEndian.Uint32(raw[5:9]), TimestampMS: binary.BigEndian.Uint64(raw[9:17]), FragmentIndex: raw[17], FragmentCount: raw[18], Payload: append([]byte(nil), raw[frameHeaderSize:]...), }, nil } func FragmentPayload(channel byte, sequence uint32, timestampMS uint64, payload []byte) ([]Frame, error) { limit, ok := channelLimit(channel) if !ok { return nil, ErrFrameChannel } if limit > 1179 { limit = 1179 } count := (len(payload) + limit - 1) / limit if count == 0 { count = 1 } if count > maxFragmentCount { return nil, ErrFrameFragmentedLimit } frames := make([]Frame, 0, count) for index := 0; index < count; index++ { start := index * limit end := start + limit if end > len(payload) { end = len(payload) } frames = append(frames, Frame{ Channel: channel, Sequence: sequence, TimestampMS: timestampMS, FragmentIndex: byte(index), FragmentCount: byte(count), Payload: append([]byte(nil), payload[start:end]...), }) } return frames, nil } func ValidateFrame(raw []byte) error { _, err := DecodeFrame(raw) return err } func FrameError(channel byte, err error) error { return fmt.Errorf("channel %d: %w", channel, err) }