Files
RemoteDesk/transport/hysteria2-agent/media_test.go
T
曾志威 19a8e03a83
ci / rust (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / package-preview (push) Canceled after 0s
ci / package-installer (push) Canceled after 0s
ci / linux-agent (push) Canceled after 0s
ci / edge-service (push) Canceled after 0s
ci / coturn-pop (push) Canceled after 0s
ci / package-windows-host (push) Canceled after 0s
Document all-Rust migration and extend native media stack
2026-08-14 14:31:57 +08:00

303 lines
8.3 KiB
Go

package main
import (
"bytes"
"context"
"encoding/binary"
"testing"
"time"
)
type testEncodedRing struct {
records []*EncodedRingRecord
}
func (r *testEncodedRing) Pop() (*EncodedRingRecord, error) {
if len(r.records) == 0 {
return nil, nil
}
record := r.records[0]
r.records = r.records[1:]
return record, nil
}
func (*testEncodedRing) Close() error { return nil }
func TestVideoAccessUnitRoundTripThroughFragments(t *testing.T) {
payload := bytes.Repeat([]byte{0x5a}, videoDatagramPayloadMax*2+17)
unit := VideoAccessUnit{
StreamID: 1,
Generation: 2,
Sequence: 3,
FrameID: 4,
PTS: 5,
DTS: 6,
Flags: videoFlagKeyFrame,
Payload: payload,
}
datagrams, err := fragmentVideoAccessUnit(unit)
if err != nil {
t.Fatal(err)
}
if len(datagrams) != 3 {
t.Fatalf("got %d datagrams, want 3", len(datagrams))
}
reassembler := newVideoReassembler()
var recovered *VideoAccessUnit
now := time.Unix(100, 0)
for _, datagram := range datagrams {
recovered, err = reassembler.push(datagram, now)
if err != nil {
t.Fatal(err)
}
}
if recovered == nil {
t.Fatal("frame was not reassembled")
}
if !bytes.Equal(recovered.Payload, payload) || recovered.Flags&videoFlagKeyFrame == 0 {
t.Fatal("reassembled frame does not match source")
}
}
func TestVideoReassemblerExpiresIncompleteFrame(t *testing.T) {
datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{
StreamID: 1,
Generation: 1,
Sequence: 1,
FrameID: 1,
PTS: 1,
DTS: 1,
Flags: videoFlagKeyFrame,
Payload: bytes.Repeat([]byte{1}, videoDatagramPayloadMax+1),
})
if err != nil {
t.Fatal(err)
}
reassembler := newVideoReassembler()
now := time.Unix(200, 0)
if _, err := reassembler.push(datagrams[0], now); err != nil {
t.Fatal(err)
}
if _, err := reassembler.push(datagrams[0], now.Add(videoReassemblyTimeout+time.Nanosecond)); err != nil {
t.Fatal(err)
}
if len(reassembler.frames) != 1 {
t.Fatalf("expected only the new incomplete assembly, got %d", len(reassembler.frames))
}
}
func TestVideoDatagramRejectsInvalidFragmentFlags(t *testing.T) {
datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{
StreamID: 1,
Generation: 1,
Sequence: 1,
FrameID: 1,
Payload: []byte{1},
})
if err != nil {
t.Fatal(err)
}
datagrams[0][56] = 0
datagrams[0][57] = 0
if _, err := decodeVideoDatagram(datagrams[0]); err == nil {
t.Fatal("invalid first/last flags were accepted")
}
}
func TestVideoDatagramSizeUsesCompleteDatagramLimit(t *testing.T) {
if err := validateVideoDatagramSize(make([]byte, videoDatagramMaxBytes)); err != nil {
t.Fatal(err)
}
if err := validateVideoDatagramSize(make([]byte, videoDatagramMaxBytes+1)); err == nil {
t.Fatal("oversized datagram was accepted")
}
}
func TestPrepareDatagramKeepsOpaquePacketsUnparsed(t *testing.T) {
input := []byte("audio-or-control")
prepared, kind, err := prepareDatagram(input)
if err != nil {
t.Fatal(err)
}
if kind != datagramOpaque || !bytes.Equal(prepared, input) {
t.Fatalf("unexpected opaque packet result: kind=%d payload=%q", kind, prepared)
}
prepared[0] = 'X'
if input[0] == 'X' {
t.Fatal("prepared datagram aliases the UDP input buffer")
}
}
func TestPrepareDatagramRecognizesVideoPackets(t *testing.T) {
datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{
StreamID: 1, Generation: 1, Sequence: 1, FrameID: 1, Payload: []byte{1},
})
if err != nil {
t.Fatal(err)
}
_, kind, err := prepareDatagram(datagrams[0])
if err != nil {
t.Fatal(err)
}
if kind != datagramVideo {
t.Fatalf("got datagram kind %d, want video", kind)
}
}
func TestAudioPacketRoundTripIsIndependent(t *testing.T) {
input, err := encodeAudioDatagram(AudioPacket{
StreamID: 1, Generation: 2, Sequence: 3, PTS: 4, DurationMS: 20,
Payload: []byte{0x01, 0x02, 0x03},
})
if err != nil {
t.Fatal(err)
}
decoded, err := decodeAudioDatagram(input)
if err != nil {
t.Fatal(err)
}
if decoded.StreamID != 1 || decoded.Generation != 2 || decoded.Sequence != 3 ||
decoded.PTS != 4 || decoded.DurationMS != 20 || !bytes.Equal(decoded.Payload, []byte{1, 2, 3}) {
t.Fatalf("decoded audio packet does not match source: %+v", decoded)
}
prepared, kind, err := prepareDatagram(input)
if err != nil {
t.Fatal(err)
}
if kind != datagramOpaque || !bytes.Equal(prepared, input) {
t.Fatalf("audio packet was not preserved as opaque transport data")
}
}
func TestAudioDatagramRejectsInvalidDurationAndSize(t *testing.T) {
if _, err := encodeAudioDatagram(AudioPacket{
StreamID: 1, Generation: 1, Sequence: 1, PTS: 1, DurationMS: 15, Payload: []byte{1},
}); err == nil {
t.Fatal("invalid audio duration was accepted")
}
if err := validateAudioDatagramSize(make([]byte, audioDatagramMaxBytes+1)); err == nil {
t.Fatal("oversized audio datagram was accepted")
}
}
func TestAudioDatagramRejectsReservedByteAndUnknownFlags(t *testing.T) {
encoded, err := encodeAudioDatagram(AudioPacket{
StreamID: 1, Generation: 2, Sequence: 3, PTS: 4,
DurationMS: 20, Payload: []byte("opus"),
})
if err != nil {
t.Fatal(err)
}
encoded[5] = 1
if _, err := decodeAudioDatagram(encoded); err == nil {
t.Fatal("audio reserved byte was accepted")
}
encoded[5] = 0
binary.LittleEndian.PutUint16(encoded[38:40], 2)
if _, err := decodeAudioDatagram(encoded); err == nil {
t.Fatal("unknown audio flags were accepted")
}
}
func TestPumpEncodedRingFragmentsAndMarksKeyFrame(t *testing.T) {
payload := bytes.Repeat([]byte{0x42}, videoDatagramPayloadMax+3)
ring := &testEncodedRing{records: []*EncodedRingRecord{{
Sequence: 9, PTS: 123, KeyFrame: true, Payload: payload,
}}}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var datagrams [][]byte
send := func(data []byte) error {
datagrams = append(datagrams, append([]byte(nil), data...))
if len(datagrams) == 2 {
cancel()
}
return nil
}
err := pumpEncodedRing(ctx, ring, 7, 8, send)
if err != context.Canceled {
t.Fatalf("pump returned %v, want context cancellation", err)
}
if len(datagrams) != 2 {
t.Fatalf("got %d datagrams, want 2", len(datagrams))
}
first, err := decodeVideoDatagram(datagrams[0])
if err != nil {
t.Fatal(err)
}
if first.Flags&videoFlagKeyFrame == 0 || first.PTS != 123 || first.FrameID != 9 {
t.Fatalf("ring metadata was not preserved: %+v", first)
}
}
func TestPumpEncodedRingRejectsInvalidRecord(t *testing.T) {
ring := &testEncodedRing{records: []*EncodedRingRecord{{Sequence: 0, Payload: []byte{1}}}}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := pumpEncodedRing(ctx, ring, 1, 1, func([]byte) error { return nil })
if err == nil {
t.Fatal("invalid encoded ring record was accepted")
}
}
func TestPumpAudioRingEncodesRDA1AndDiscontinuity(t *testing.T) {
ring := &testEncodedRing{records: []*EncodedRingRecord{{
Sequence: 7,
PTS: 400_000,
Kind: 2,
Payload: []byte("opus"),
}}}
ctx, cancel := context.WithCancel(context.Background())
var datagram []byte
err := pumpAudioRing(ctx, ring, 11, 12, func(data []byte) error {
datagram = append([]byte(nil), data...)
cancel()
return nil
})
if err != context.Canceled {
t.Fatalf("pump returned %v, want context cancellation", err)
}
packet, err := decodeAudioDatagram(datagram)
if err != nil {
t.Fatal(err)
}
if packet.StreamID != 11 || packet.Generation != 12 || packet.Sequence != 7 ||
packet.PTS != 400_000 || packet.DurationMS != 20 ||
packet.Flags&audioFlagDiscontinuity == 0 || !bytes.Equal(packet.Payload, []byte("opus")) {
t.Fatalf("audio ring metadata was not preserved: %+v", packet)
}
}
func TestPumpAudioRingRejectsInvalidRecords(t *testing.T) {
tests := []struct {
name string
record *EncodedRingRecord
}{
{name: "zero sequence", record: &EncodedRingRecord{Payload: []byte{1}}},
{name: "empty payload", record: &EncodedRingRecord{Sequence: 1}},
{
name: "oversized payload",
record: &EncodedRingRecord{
Sequence: 1,
Payload: make([]byte, audioPacketMaxBytes+1),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ring := &testEncodedRing{records: []*EncodedRingRecord{test.record}}
err := pumpAudioRing(
context.Background(),
ring,
1,
1,
func([]byte) error { return nil },
)
if err == nil {
t.Fatal("invalid audio ring record was accepted")
}
})
}
}