feat(gateway): relay complete encoded frames

This commit is contained in:
sechmachine
2026-07-30 22:47:48 +07:00
parent f12ed6c685
commit 786c96b110
22 changed files with 1023 additions and 159 deletions
+271 -9
View File
@@ -1,6 +1,7 @@
package gateway
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
@@ -32,7 +33,7 @@ const protocolTerminalReceiptVector = "VGF1\x00\x03\x00\x00"
func TestFrameValidationAndFragmentation(t *testing.T) {
frames, err := FragmentPayload(ChannelVideo, 7, 11, make([]byte, 1180))
if err != nil || len(frames) != 2 || len(frames[0].Payload) != 1179 || len(frames[1].Payload) != 1 {
if err != nil || len(frames) != 2 || len(frames[0].Payload) != 1177 || len(frames[1].Payload) != 3 {
t.Fatalf("fragmentation = %#v, err = %v", frames, err)
}
encoded, err := EncodeFrame(frames[0])
@@ -161,11 +162,11 @@ func TestNewServerRejectsPartiallyConfiguredCapabilities(t *testing.T) {
}
func TestSyntheticImpairmentPacingAndResourceBounds(t *testing.T) {
payload := make([]byte, 1179*16+1)
payload := make([]byte, maxCompleteFrameBytes+1)
if _, err := FragmentPayload(ChannelVideo, 1, 0, payload); !errors.Is(err, ErrFrameFragmentedLimit) {
t.Fatalf("oversized media payload accepted: %v", err)
}
frames, err := FragmentPayload(ChannelVideo, 1, 0, bytesRepeat(0x5a, 1179*4))
frames, err := FragmentPayload(ChannelVideo, 1, 0, bytesRepeat(0x5a, frameV2PayloadSize*4))
if err != nil {
t.Fatal(err)
}
@@ -186,11 +187,29 @@ func TestSyntheticImpairmentPacingAndResourceBounds(t *testing.T) {
}
deliveredBytes += len(decoded.Payload)
}
if delivered != 3 || deliveredBytes != 1179*3 {
if delivered != 3 || deliveredBytes != frameV2PayloadSize*3 {
t.Fatalf("synthetic impairment delivered=%d bytes=%d", delivered, deliveredBytes)
}
}
func TestFragmentPayloadCarriesCompleteEncodedFrame(t *testing.T) {
payload := bytesRepeat(0x5a, 256*1024)
frames, err := FragmentPayload(ChannelVideo, 9, 11, payload)
if errors.Is(err, ErrFrameFragmentedLimit) {
t.Fatalf("complete encoded frame rejected at legacy fragment ceiling: %v", err)
}
if err != nil {
t.Fatal(err)
}
var recovered []byte
for _, frame := range frames {
recovered = append(recovered, frame.Payload...)
}
if !bytes.Equal(recovered, payload) {
t.Fatal("complete encoded frame payload changed")
}
}
func TestApolloFixturesAndLifecycle(t *testing.T) {
management, err := os.ReadFile("testdata/apollo-management.xml")
if err != nil {
@@ -218,10 +237,10 @@ func TestApolloFixturesAndLifecycle(t *testing.T) {
t.Fatal(err)
}
if got := <-session.Video(); string(got.Payload) != string(video) {
t.Fatalf("video changed: %x", got)
t.Fatalf("video changed: %x", got.Payload)
}
if got := <-session.Audio(); string(got.Payload) != string(audio) {
t.Fatalf("audio changed: %x", got)
t.Fatalf("audio changed: %x", got.Payload)
}
if err := session.Input(context.Background(), InputEvent{Sequence: 1, Device: "keyboard", Code: 7, Pressed: true}); err != nil {
t.Fatal(err)
@@ -401,7 +420,7 @@ func TestGatewayTelemetrySeparatesQueueProcessingAndPacing(t *testing.T) {
t.Fatal(err)
}
receiveCtx, receiveCancel := context.WithTimeout(context.Background(), 2*time.Second)
for index := byte(0); index < 2; index++ {
for index := uint16(0); index < 2; index++ {
frame, err := client.ReceiveFrame(receiveCtx)
if err != nil {
t.Fatal(err)
@@ -838,6 +857,7 @@ func newNativeGatewayLifecycleHarness(t *testing.T, sessionID string) nativeGate
type independentGatewayClient struct {
connection *quic.Conn
control *quic.Stream
media independentMediaReassembler
}
func dialIndependentGateway(ctx context.Context, address string, tlsConfig *tls.Config, request protocol.TunnelAdmissionRequest) (*independentGatewayClient, error) {
@@ -866,7 +886,11 @@ func dialIndependentGateway(ctx context.Context, address string, tlsConfig *tls.
_ = connection.CloseWithError(applicationError, "independent client admission failed")
return nil, err
}
return &independentGatewayClient{connection: connection, control: stream}, nil
return &independentGatewayClient{
connection: connection,
control: stream,
media: independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)},
}, nil
}
func (c *independentGatewayClient) ReceiveProviderEvent(ctx context.Context) (ProviderEvent, error) {
@@ -912,7 +936,245 @@ func (c *independentGatewayClient) ReceiveFrame(ctx context.Context) (Frame, err
if err != nil {
return Frame{}, err
}
return DecodeFrame(data)
return independentDecodeFrame(data)
}
func (c *independentGatewayClient) ReceiveMedia(ctx context.Context) ([]byte, error) {
for {
data, err := c.connection.ReceiveDatagram(ctx)
if err != nil {
return nil, err
}
payload, complete, err := c.media.Add(data, time.Now())
if err != nil {
return nil, err
}
if complete {
return payload, nil
}
}
}
type independentMediaKey struct {
channel byte
sequence uint32
}
type independentMediaUnit struct {
started time.Time
timestamp uint64
fragments [][]byte
received []bool
bytes int
}
type independentMediaReassembler struct {
incomplete map[independentMediaKey]*independentMediaUnit
}
func independentDecodeFrame(data []byte) (Frame, error) {
const (
v1Header, v2Header = 21, 23
v1Payload, v2Payload = 1179, 1177
)
if len(data) < 3 {
return Frame{}, ErrFrameTruncated
}
if data[0] != 'V' || data[1] != 'D' {
return Frame{}, ErrFrameMagic
}
version := data[2]
headerSize, payloadLimit := v1Header, v1Payload
if version == 2 {
headerSize, payloadLimit = v2Header, v2Payload
} else if version != 1 {
return Frame{}, ErrFrameVersion
}
if len(data) < headerSize || version == 2 && len(data) > 1200 {
return Frame{}, ErrFrameSize
}
frame := Frame{
Version: version,
Channel: data[3],
Flags: data[4],
Sequence: binary.BigEndian.Uint32(data[5:9]),
TimestampMS: binary.BigEndian.Uint64(data[9:17]),
}
payloadLengthOffset := 19
if version == 1 {
frame.FragmentIndex = uint16(data[17])
frame.FragmentCount = uint16(data[18])
} else {
frame.FragmentIndex = binary.BigEndian.Uint16(data[17:19])
frame.FragmentCount = binary.BigEndian.Uint16(data[19:21])
payloadLengthOffset = 21
}
if (frame.Channel != ChannelVideo && frame.Channel != ChannelAudio) || frame.Flags != 0 ||
frame.FragmentCount == 0 || version == 1 && frame.FragmentCount > 16 ||
version == 2 && frame.FragmentCount > 891 || frame.FragmentIndex >= frame.FragmentCount {
return Frame{}, ErrFrameFragment
}
payloadLength := int(binary.BigEndian.Uint16(data[payloadLengthOffset : payloadLengthOffset+2]))
if payloadLength > payloadLimit || len(data) != headerSize+payloadLength {
return Frame{}, ErrFrameLength
}
frame.Payload = append([]byte(nil), data[headerSize:]...)
return frame, nil
}
func (r *independentMediaReassembler) Add(data []byte, now time.Time) ([]byte, bool, error) {
frame, err := independentDecodeFrame(data)
if err != nil {
return nil, false, err
}
for key, unit := range r.incomplete {
if now.Sub(unit.started) > 250*time.Millisecond {
delete(r.incomplete, key)
}
}
key := independentMediaKey{channel: frame.Channel, sequence: frame.Sequence}
unit := r.incomplete[key]
if unit == nil {
if len(r.incomplete) == 4 {
var oldestKey independentMediaKey
var oldest time.Time
for candidate, current := range r.incomplete {
if oldest.IsZero() || current.started.Before(oldest) {
oldestKey, oldest = candidate, current.started
}
}
delete(r.incomplete, oldestKey)
}
unit = &independentMediaUnit{
started: now, timestamp: frame.TimestampMS,
fragments: make([][]byte, frame.FragmentCount), received: make([]bool, frame.FragmentCount),
}
r.incomplete[key] = unit
}
if len(unit.fragments) != int(frame.FragmentCount) || unit.timestamp != frame.TimestampMS {
return nil, false, ErrFrameFragment
}
index := int(frame.FragmentIndex)
if unit.received[index] {
if !bytes.Equal(unit.fragments[index], frame.Payload) {
return nil, false, ErrFrameFragment
}
return nil, false, nil
}
if unit.bytes+len(frame.Payload) > 1<<20 {
delete(r.incomplete, key)
return nil, false, ErrFrameSize
}
unit.fragments[index] = frame.Payload
unit.received[index] = true
unit.bytes += len(frame.Payload)
for _, received := range unit.received {
if !received {
return nil, false, nil
}
}
payload := make([]byte, 0, unit.bytes)
for _, fragment := range unit.fragments {
payload = append(payload, fragment...)
}
delete(r.incomplete, key)
return payload, true, nil
}
func TestIndependentClientReassemblesProtocolDatagramV2(t *testing.T) {
fixed, err := hex.DecodeString("5644020a00000000010000000000000002000000010003010203")
if err != nil {
t.Fatal(err)
}
frame, err := independentDecodeFrame(fixed)
if err != nil || frame.Version != 2 || frame.Channel != ChannelVideo ||
frame.Sequence != 1 || frame.TimestampMS != 2 || frame.FragmentCount != 1 ||
!bytes.Equal(frame.Payload, []byte{1, 2, 3}) {
t.Fatalf("fixed Protocol v2 frame = %#v, %v", frame, err)
}
payload := bytes.Repeat([]byte("frame-boundary-"), 300)
fragments, err := FragmentPayload(ChannelVideo, 7, 11, payload)
if err != nil || len(fragments) < 3 {
t.Fatalf("fragments = %d, %v", len(fragments), err)
}
encoded := make([][]byte, len(fragments))
for index, fragment := range fragments {
encoded[index], err = EncodeFrame(fragment)
if err != nil {
t.Fatal(err)
}
}
reassembler := independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)}
now := time.Unix(0, 0)
order := []int{2, 0, 0, 1, 3}
var recovered []byte
for _, index := range order {
var complete bool
recovered, complete, err = reassembler.Add(encoded[index], now)
if err != nil {
t.Fatal(err)
}
if index != 3 && complete {
t.Fatalf("unit completed at fragment %d", index)
}
}
if !bytes.Equal(recovered, payload) {
t.Fatal("independent client changed the complete encoded frame")
}
if _, _, err := reassembler.Add(encoded[0], now); err != nil {
t.Fatal(err)
}
conflict := append([]byte(nil), encoded[0]...)
conflict[len(conflict)-1] ^= 0xff
if _, _, err := reassembler.Add(conflict, now); !errors.Is(err, ErrFrameFragment) {
t.Fatalf("conflicting duplicate = %v", err)
}
reassembler = independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)}
if _, _, err := reassembler.Add(encoded[0], now); err != nil {
t.Fatal(err)
}
expired := append([]byte(nil), encoded[0]...)
binary.BigEndian.PutUint32(expired[5:9], 8)
if _, _, err := reassembler.Add(expired, now.Add(251*time.Millisecond)); err != nil {
t.Fatal(err)
}
if len(reassembler.incomplete) != 1 {
t.Fatalf("expired incomplete units = %d, want 1", len(reassembler.incomplete))
}
reassembler = independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)}
for sequence := uint32(1); sequence <= 5; sequence++ {
partial, encodeErr := EncodeFrame(Frame{
Version: 2, Channel: ChannelVideo, Sequence: sequence, TimestampMS: 1,
FragmentIndex: 0, FragmentCount: 2, Payload: []byte{byte(sequence)},
})
if encodeErr != nil {
t.Fatal(encodeErr)
}
if _, _, err := reassembler.Add(partial, now.Add(time.Duration(sequence)*time.Nanosecond)); err != nil {
t.Fatal(err)
}
}
if len(reassembler.incomplete) != 4 || reassembler.incomplete[independentMediaKey{channel: ChannelVideo, sequence: 1}] != nil {
t.Fatalf("fifth unit did not evict the oldest: %#v", reassembler.incomplete)
}
reassembler = independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)}
for index := uint16(0); index < 891; index++ {
fragment, encodeErr := EncodeFrame(Frame{
Version: 2, Channel: ChannelVideo, Sequence: 99, TimestampMS: 1,
FragmentIndex: index, FragmentCount: 891, Payload: make([]byte, 1177),
})
if encodeErr != nil {
t.Fatal(encodeErr)
}
_, _, err = reassembler.Add(fragment, now)
}
if !errors.Is(err, ErrFrameSize) || len(reassembler.incomplete) != 0 {
t.Fatalf("oversized reassembly = %v, incomplete=%d", err, len(reassembler.incomplete))
}
}
func (c *independentGatewayClient) waitClosed(t *testing.T) {