172 lines
4.5 KiB
Go
172 lines
4.5 KiB
Go
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)
|
|
}
|