feat(gateway): relay complete encoded frames
This commit is contained in:
+96
-34
@@ -7,9 +7,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
frameHeaderSize = 21
|
||||
maxFrameSize = 1 << 16
|
||||
maxFragmentCount = 16
|
||||
frameV1HeaderSize = 21
|
||||
frameV2HeaderSize = 23
|
||||
frameV1PayloadSize = 1179
|
||||
frameV2PayloadSize = 1177
|
||||
maxV1FragmentCount = 16
|
||||
maxV2FragmentCount = 891
|
||||
maxCompleteFrameBytes = 1 << 20
|
||||
maxFrameSize = 1 << 16
|
||||
frameHeaderSize = frameV2HeaderSize
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -35,16 +41,25 @@ var (
|
||||
)
|
||||
|
||||
type Frame struct {
|
||||
Version byte
|
||||
Channel byte
|
||||
Flags byte
|
||||
Sequence uint32
|
||||
TimestampMS uint64
|
||||
FragmentIndex byte
|
||||
FragmentCount byte
|
||||
FragmentIndex uint16
|
||||
FragmentCount uint16
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func channelLimit(channel byte) (int, bool) {
|
||||
func channelLimit(version, channel byte) (int, bool) {
|
||||
if version == 2 {
|
||||
switch channel {
|
||||
case ChannelVideo, ChannelAudio:
|
||||
return frameV2PayloadSize, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
switch channel {
|
||||
case ChannelControl:
|
||||
return 1024, true
|
||||
@@ -53,93 +68,139 @@ func channelLimit(channel byte) (int, bool) {
|
||||
case ChannelText:
|
||||
return 65515, true
|
||||
case ChannelVideo, ChannelAudio, ChannelInput:
|
||||
return 1179, true
|
||||
return frameV1PayloadSize, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func EncodeFrame(frame Frame) ([]byte, error) {
|
||||
limit, ok := channelLimit(frame.Channel)
|
||||
version := frame.Version
|
||||
if version == 0 {
|
||||
version = 1
|
||||
}
|
||||
if version != 1 && version != 2 {
|
||||
return nil, ErrFrameVersion
|
||||
}
|
||||
limit, ok := channelLimit(version, 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 {
|
||||
maxFragments := uint16(maxV1FragmentCount)
|
||||
headerSize := frameV1HeaderSize
|
||||
if version == 2 {
|
||||
maxFragments = maxV2FragmentCount
|
||||
headerSize = frameV2HeaderSize
|
||||
}
|
||||
if frame.FragmentCount == 0 || frame.FragmentCount > maxFragments || frame.FragmentIndex >= frame.FragmentCount {
|
||||
return nil, ErrFrameFragment
|
||||
}
|
||||
if len(frame.Payload) > limit {
|
||||
return nil, ErrFramePayloadLimit
|
||||
}
|
||||
if len(frame.Payload) > maxFrameSize-frameHeaderSize {
|
||||
if len(frame.Payload) > 1<<16-headerSize {
|
||||
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
|
||||
encoded := make([]byte, headerSize+len(frame.Payload))
|
||||
encoded[0], encoded[1], encoded[2], encoded[3], encoded[4] = 'V', 'D', version, 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)
|
||||
if version == 1 {
|
||||
encoded[17], encoded[18] = byte(frame.FragmentIndex), byte(frame.FragmentCount)
|
||||
binary.BigEndian.PutUint16(encoded[19:21], uint16(len(frame.Payload)))
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(encoded[17:19], frame.FragmentIndex)
|
||||
binary.BigEndian.PutUint16(encoded[19:21], frame.FragmentCount)
|
||||
binary.BigEndian.PutUint16(encoded[21:23], uint16(len(frame.Payload)))
|
||||
}
|
||||
copy(encoded[headerSize:], frame.Payload)
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
func DecodeFrame(raw []byte) (Frame, error) {
|
||||
if len(raw) < frameHeaderSize {
|
||||
if len(raw) < 3 {
|
||||
return Frame{}, ErrFrameTruncated
|
||||
}
|
||||
if len(raw) > maxFrameSize {
|
||||
return Frame{}, ErrFrameSize
|
||||
}
|
||||
if raw[0] != 'V' || raw[1] != 'D' {
|
||||
return Frame{}, ErrFrameMagic
|
||||
}
|
||||
if raw[2] != 1 {
|
||||
version := raw[2]
|
||||
if version != 1 && version != 2 {
|
||||
return Frame{}, ErrFrameVersion
|
||||
}
|
||||
limit, ok := channelLimit(raw[3])
|
||||
headerSize := frameV1HeaderSize
|
||||
maxFragments := uint16(maxV1FragmentCount)
|
||||
if version == 2 {
|
||||
headerSize = frameV2HeaderSize
|
||||
maxFragments = maxV2FragmentCount
|
||||
}
|
||||
if len(raw) < headerSize {
|
||||
return Frame{}, ErrFrameTruncated
|
||||
}
|
||||
if version == 1 && len(raw) > 1<<16 || version == 2 && len(raw) > 1200 {
|
||||
return Frame{}, ErrFrameSize
|
||||
}
|
||||
limit, ok := channelLimit(version, 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] {
|
||||
var fragmentIndex, fragmentCount uint16
|
||||
payloadOffset := 19
|
||||
if version == 1 {
|
||||
fragmentIndex, fragmentCount = uint16(raw[17]), uint16(raw[18])
|
||||
} else {
|
||||
fragmentIndex = binary.BigEndian.Uint16(raw[17:19])
|
||||
fragmentCount = binary.BigEndian.Uint16(raw[19:21])
|
||||
payloadOffset = 21
|
||||
}
|
||||
if fragmentCount == 0 || fragmentCount > maxFragments || fragmentIndex >= fragmentCount {
|
||||
return Frame{}, ErrFrameFragment
|
||||
}
|
||||
payloadLength := int(binary.BigEndian.Uint16(raw[19:21]))
|
||||
payloadLength := int(binary.BigEndian.Uint16(raw[payloadOffset : payloadOffset+2]))
|
||||
if payloadLength > limit {
|
||||
return Frame{}, ErrFramePayloadLimit
|
||||
}
|
||||
if len(raw) != frameHeaderSize+payloadLength {
|
||||
if len(raw) != headerSize+payloadLength {
|
||||
return Frame{}, ErrFrameLength
|
||||
}
|
||||
return Frame{
|
||||
Version: version,
|
||||
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:]...),
|
||||
FragmentIndex: fragmentIndex,
|
||||
FragmentCount: fragmentCount,
|
||||
Payload: append([]byte(nil), raw[headerSize:]...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func FragmentPayload(channel byte, sequence uint32, timestampMS uint64, payload []byte) ([]Frame, error) {
|
||||
limit, ok := channelLimit(channel)
|
||||
if !ok {
|
||||
version := byte(1)
|
||||
limit := frameV1PayloadSize
|
||||
maxFragments := maxV1FragmentCount
|
||||
if channel == ChannelVideo || channel == ChannelAudio {
|
||||
version = 2
|
||||
limit = frameV2PayloadSize
|
||||
maxFragments = maxV2FragmentCount
|
||||
}
|
||||
if _, ok := channelLimit(version, channel); !ok {
|
||||
return nil, ErrFrameChannel
|
||||
}
|
||||
if limit > 1179 {
|
||||
limit = 1179
|
||||
if len(payload) > maxCompleteFrameBytes {
|
||||
return nil, ErrFrameFragmentedLimit
|
||||
}
|
||||
count := (len(payload) + limit - 1) / limit
|
||||
if count == 0 {
|
||||
count = 1
|
||||
}
|
||||
if count > maxFragmentCount {
|
||||
if count > maxFragments {
|
||||
return nil, ErrFrameFragmentedLimit
|
||||
}
|
||||
frames := make([]Frame, 0, count)
|
||||
@@ -150,11 +211,12 @@ func FragmentPayload(channel byte, sequence uint32, timestampMS uint64, payload
|
||||
end = len(payload)
|
||||
}
|
||||
frames = append(frames, Frame{
|
||||
Version: version,
|
||||
Channel: channel,
|
||||
Sequence: sequence,
|
||||
TimestampMS: timestampMS,
|
||||
FragmentIndex: byte(index),
|
||||
FragmentCount: byte(count),
|
||||
FragmentIndex: uint16(index),
|
||||
FragmentCount: uint16(count),
|
||||
Payload: append([]byte(nil), payload[start:end]...),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user