fix(gateway): close Phase 3C audit gaps

This commit is contained in:
sechmachine
2026-07-30 01:46:00 +07:00
parent 040ca73ce9
commit d3852d15f3
23 changed files with 1619 additions and 219 deletions
+37 -21
View File
@@ -22,21 +22,46 @@ type apolloAudioAssembler struct {
blocks map[uint16]*apolloAudioFECBlock
}
func (a *apolloAudioAssembler) Add(codec *apolloMediaCodec, shard apolloAudioShard) ([][]byte, error) {
func (a *apolloAudioAssembler) Add(codec *apolloMediaCodec, shard apolloAudioShard) ([][]byte, bool, error) {
if codec == nil || len(shard.payload) == 0 || len(shard.payload) > 1408 || len(shard.payload)%16 != 0 {
return nil, errApolloMedia
return nil, false, errApolloMedia
}
if a.blocks == nil {
a.blocks = make(map[uint16]*apolloAudioFECBlock)
}
base := shard.base
if base&3 != 0 {
return nil, errApolloMedia
return nil, false, errApolloMedia
}
index := 0
if shard.parity {
if shard.parityIndex >= apolloAudioParityShards {
return nil, false, errApolloMedia
}
index = apolloAudioDataShards + int(shard.parityIndex)
} else {
index = int(uint16(shard.sequence - base))
if index >= apolloAudioDataShards {
return nil, false, errApolloMedia
}
}
evicted := false
block := a.blocks[base]
if block == nil {
if len(a.blocks) >= apolloAudioMaximumBlocks {
return nil, errApolloMedia
var oldest uint16
var maximumAge uint16
for candidate := range a.blocks {
age := base - candidate
if age > maximumAge && age < 1<<15 {
oldest, maximumAge = candidate, age
}
}
if maximumAge == 0 {
return nil, false, errApolloMedia
}
delete(a.blocks, oldest)
evicted = true
}
block = &apolloAudioFECBlock{base: base}
a.blocks[base] = block
@@ -44,49 +69,40 @@ func (a *apolloAudioAssembler) Add(codec *apolloMediaCodec, shard apolloAudioSha
if block.size == 0 {
block.size = len(shard.payload)
} else if block.size != len(shard.payload) {
return nil, errApolloMedia
return nil, evicted, errApolloMedia
}
index := 0
if shard.parity {
if shard.parityIndex >= apolloAudioParityShards {
return nil, errApolloMedia
}
index = apolloAudioDataShards + int(shard.parityIndex)
if block.haveFEC && (block.timestamp != shard.timestamp || block.ssrc != shard.ssrc) {
return nil, errApolloMedia
return nil, evicted, errApolloMedia
}
block.timestamp, block.ssrc, block.haveFEC = shard.timestamp, shard.ssrc, true
} else {
index = int(uint16(shard.sequence - base))
if index >= apolloAudioDataShards {
return nil, errApolloMedia
}
if block.haveFEC && (shard.timestamp != block.timestamp+uint32(index*5) || shard.ssrc != block.ssrc) {
return nil, errApolloMedia
return nil, evicted, errApolloMedia
}
}
if block.received[index] {
return nil, errApolloMedia
return nil, evicted, errApolloMedia
}
block.shards[index] = append([]byte(nil), shard.payload...)
block.received[index] = true
block.count++
if block.count < apolloAudioDataShards {
return nil, nil
return nil, evicted, nil
}
if err := reconstructApolloAudioBlock(block); err != nil {
return nil, err
return nil, evicted, err
}
output := make([][]byte, apolloAudioDataShards)
for index := range output {
payload, err := codec.openApolloAudioCipher(base+uint16(index), block.shards[index])
if err != nil {
return nil, err
return nil, evicted, err
}
output[index] = payload
}
delete(a.blocks, base)
return output, nil
return output, evicted, nil
}
func reconstructApolloAudioBlock(block *apolloAudioFECBlock) error {
+28 -8
View File
@@ -43,7 +43,8 @@ func NewNativeApolloBackend() *NativeApolloBackend {
func (b *NativeApolloBackend) Management(ctx context.Context, request LaunchRequest) ([]byte, error) {
work := request.ProviderWork
if err := work.Validate(); err != nil || request.SessionID == "" || request.SessionID != work.SessionID || work.ProviderProfile != ProviderProfileApollo {
if err := work.Validate(); err != nil || validateApolloStreamPolicy(work.StreamPolicy) != nil ||
request.SessionID == "" || request.SessionID != work.SessionID || work.ProviderProfile != ProviderProfileApollo {
return nil, ErrProviderMalformed
}
client, err := newPinnedApolloHTTPClient(work)
@@ -199,7 +200,8 @@ func pinnedApolloTLSConfig(work protocol.ProviderSessionWork) (*tls.Config, erro
func (b *NativeApolloBackend) Setup(ctx context.Context, request LaunchRequest) ([]byte, error) {
work := request.ProviderWork
if err := work.Validate(); err != nil || request.SessionID == "" || request.SessionID != work.SessionID || work.ProviderProfile != ProviderProfileApollo {
if err := work.Validate(); err != nil || validateApolloStreamPolicy(work.StreamPolicy) != nil ||
request.SessionID == "" || request.SessionID != work.SessionID || work.ProviderProfile != ProviderProfileApollo {
return nil, ErrProviderMalformed
}
client, err := newPinnedApolloHTTPClient(work)
@@ -287,6 +289,7 @@ type nativeApolloSession struct {
state protocol.ProviderState
pressed map[string]InputEvent
closeOnce sync.Once
disconnectOnce sync.Once
channelsOnce sync.Once
done chan struct{}
readDone chan struct{}
@@ -493,6 +496,9 @@ func (s *nativeApolloSession) ReleaseAll(ctx context.Context) error {
func (s *nativeApolloSession) Terminate(ctx context.Context) error {
var cleanupErr error
s.mu.Lock()
disconnected := s.state.State == ProviderStateDisconnected
s.mu.Unlock()
s.closeOnce.Do(func() {
if err := s.ReleaseAll(ctx); err != nil {
cleanupErr = err
@@ -518,7 +524,7 @@ func (s *nativeApolloSession) Terminate(ctx context.Context) error {
}
if cleanupErr == nil {
s.closeMediaChannels()
if s.allowApplicationTermination {
if s.allowApplicationTermination && !disconnected {
if err := apolloCancelRequest(ctx, s.managementClient, s.managementHost, s.managementPort); err != nil {
cleanupErr = err
}
@@ -536,6 +542,8 @@ func (s *nativeApolloSession) Terminate(ctx context.Context) error {
if cleanupErr != nil {
s.state.State = ProviderStateCleanup
s.state.CleanupPending = true
} else if disconnected {
s.state.State = ProviderStateDisconnected
} else {
s.state.State = ProviderStateTerminated
}
@@ -678,11 +686,19 @@ func (s *nativeApolloSession) handleApolloDisconnect(err error) {
if err == nil {
return
}
s.mu.Lock()
if s.state.State != ProviderStateTerminated {
s.disconnectOnce.Do(func() {
s.mu.Lock()
if s.state.State == ProviderStateTerminated {
s.mu.Unlock()
return
}
s.state.State = ProviderStateDisconnected
}
s.mu.Unlock()
s.mu.Unlock()
select {
case s.events <- ProviderEvent{Kind: ProviderEventDisconnected}:
default:
}
})
}
func (s *nativeApolloSession) closeMediaChannels() {
@@ -738,7 +754,11 @@ func (s *nativeApolloSession) readUDPMedia() {
if openErr != nil {
continue
}
payloads, err = s.audioFEC.Add(s.media, shard)
var evicted bool
payloads, evicted, err = s.audioFEC.Add(s.media, shard)
if evicted {
s.mediaDrops.Add(1)
}
}
if err != nil {
continue
+58 -2
View File
@@ -55,6 +55,7 @@ func TestNativeApolloManagementUsesSessionScopedMTLS(t *testing.T) {
Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 0,
ExpiresAt: "2099-01-01T00:00:00Z", ProviderProfile: ProviderProfileApollo,
ProviderIdentity: "apollo-server#sha256:" + hex.EncodeToString(pinned[:]), PolicyVersionID: "policy-1", ApplicationID: "1", ClientID: "paired-client",
StreamPolicy: protocol.ProviderStreamPolicy{ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 8000, AudioEnabled: true},
ManagementHost: host, ManagementPort: port, StreamHost: host, StreamPort: 47984,
ClientCertificatePem: certificatePEM(t, clientTLS.Certificates[0]),
ClientPrivateKeyPem: privateKeyPEM(t, clientTLS.Certificates[0]),
@@ -70,6 +71,32 @@ func TestNativeApolloManagementUsesSessionScopedMTLS(t *testing.T) {
}
}
func TestNativeApolloSetupRejectsUnsupportedStreamPolicyBeforeProviderReadiness(t *testing.T) {
work := protocol.ProviderSessionWork{
Version: "1", SessionID: "session-1", GatewayID: "gateway-1",
ExpiresAt: "2099-01-01T00:00:00Z", ProviderProfile: ProviderProfileApollo,
ProviderIdentity: "provider#sha256:00", PolicyVersionID: "policy-1",
ApplicationID: "1", ClientID: "client-1", ManagementHost: "127.0.0.1", ManagementPort: 1,
StreamHost: "127.0.0.1", StreamPort: 1, ClientCertificatePem: "invalid",
ClientPrivateKeyPem: "invalid", ServerCertificatePem: "invalid",
ClipboardPolicy: protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30},
}
for name, policy := range map[string]protocol.ProviderStreamPolicy{
"audio-disabled": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 8000, AudioEnabled: false},
"av1": {ResolutionWidth: 3840, ResolutionHeight: 2160, Fps: 60, Codec: "AV1", BitrateKbps: 50000, AudioEnabled: true},
} {
t.Run(name, func(t *testing.T) {
work.StreamPolicy = policy
_, err := NewNativeApolloBackend().Setup(context.Background(), LaunchRequest{
SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work,
})
if !errors.Is(err, ErrProviderMalformed) {
t.Fatalf("Setup() error = %v, want ErrProviderMalformed before provider readiness", err)
}
})
}
}
func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
streamListener, err := net.Listen("tcp", "127.0.0.1:0")
@@ -158,8 +185,9 @@ func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
}
if method == "ANNOUNCE" {
for _, required := range []string{
"a=x-nv-video[0].clientViewportWd:1920", "a=x-nv-video[0].clientViewportHt:1080", "a=x-nv-video[0].maxFPS:60",
"a=x-nv-video[0].packetSize:1024", "a=x-nv-vqos[0].bw.maximumBitrateKbps:8000", "a=x-nv-audio.surround.numChannels:2",
"a=x-nv-video[0].clientViewportWd:2560", "a=x-nv-video[0].clientViewportHt:1440", "a=x-nv-video[0].maxFPS:120",
"a=x-nv-video[0].packetSize:1024", "a=x-nv-clientSupportHevc:1", "a=x-nv-vqos[0].bitStreamFormat:1",
"a=x-nv-vqos[0].bw.maximumBitrateKbps:32000", "a=x-ml-video.configuredBitrateKbps:40000", "a=x-nv-audio.surround.numChannels:2",
"a=x-nv-general.useReliableUdp:13", "a=x-ss-general.encryptionEnabled:7",
} {
if !strings.Contains(string(plaintext), required+"\r\n") {
@@ -255,6 +283,7 @@ func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) {
Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 0,
ExpiresAt: "2099-01-01T00:00:00Z", ProviderProfile: ProviderProfileApollo,
ProviderIdentity: "apollo-server#sha256:" + hex.EncodeToString(pinned[:]), PolicyVersionID: "policy-1", ApplicationID: "42", ClientID: "paired-client",
StreamPolicy: protocol.ProviderStreamPolicy{ResolutionWidth: 2560, ResolutionHeight: 1440, Fps: 120, Codec: "HEVC", BitrateKbps: 40000, AudioEnabled: true},
ManagementHost: managementHost, ManagementPort: managementPort, StreamHost: streamHost, StreamPort: streamPort,
ClientCertificatePem: certificatePEM(t, clientTLS.Certificates[0]), ClientPrivateKeyPem: privateKeyPEM(t, clientTLS.Certificates[0]),
ServerCertificatePem: certificatePEM(t, tls.Certificate{Certificate: [][]byte{serverTLS.Certificates[0].Certificate[1]}}),
@@ -625,6 +654,33 @@ func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T)
t.Fatal("encrypted FEC audio was not recovered")
}
}
for block := 0; block < apolloAudioMaximumBlocks+1; block++ {
sequence := uint16(100 + block*apolloAudioDataShards)
packet := sourceShapedEncryptedAudioPacketWithHeaders(t, key, keyID, sequence, uint32(sequence)*5, 1, []byte{byte(block)})
if _, err := audioServer.WriteToUDP(packet, audioClient.LocalAddr().(*net.UDPAddr)); err != nil {
t.Fatal(err)
}
}
for index, want := range [][]byte{{0xa0}, {0xa1}, {0xa2}, {0xa3}} {
sequence := uint16(124 + index)
packet := sourceShapedEncryptedAudioPacketWithHeaders(t, key, keyID, sequence, uint32(sequence)*5, 1, want)
if _, err := audioServer.WriteToUDP(packet, audioClient.LocalAddr().(*net.UDPAddr)); err != nil {
t.Fatal(err)
}
}
for _, want := range [][]byte{{0xa0}, {0xa1}, {0xa2}, {0xa3}} {
select {
case payload := <-session.Audio():
if string(payload) != string(want) {
t.Fatalf("post-loss audio relay = %x, want %x", payload, want)
}
case <-time.After(time.Second):
t.Fatal("sustained loss permanently stalled newer audio")
}
}
if drops := session.Telemetry().MediaDrops; drops < 2 {
t.Fatalf("stale FEC eviction drops = %d, want at least 2", drops)
}
terminateCtx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := session.Terminate(terminateCtx); err != nil {
+34 -9
View File
@@ -112,7 +112,10 @@ func (b *NativeApolloBackend) performRTSPHandshake(ctx context.Context, work pro
if err != nil {
return nil, nil, err
}
announceBody := apolloAnnounceProfile()
announceBody, err := apolloAnnounceProfile(work.StreamPolicy)
if err != nil {
return nil, nil, err
}
announce, err := request("ANNOUNCE", "streamid=control/13/0", sessionID, []apolloRTSPHeader{{"Content-Type", "application/sdp"}}, announceBody, 6)
if err != nil {
return nil, nil, err
@@ -468,18 +471,33 @@ func apolloRTSPConnectData(message apolloRTSPMessage) (uint32, error) {
return uint32(parsed), nil
}
func apolloAnnounceProfile() []byte {
func apolloAnnounceProfile(policy protocol.ProviderStreamPolicy) ([]byte, error) {
if err := validateApolloStreamPolicy(policy); err != nil {
return nil, err
}
format, supportsHEVC := int64(0), int64(0)
if policy.Codec == "HEVC" {
format, supportsHEVC = 1, 1
}
maximumBitrate := policy.BitrateKbps * 80 / 100
if maximumBitrate > 100000 {
maximumBitrate = 100000
}
return []byte("v=0\r\n" +
"o=android 0 0 IN IP4 0.0.0.0\r\n" +
"s=NVIDIA Streaming Client\r\n" +
"a=x-nv-video[0].clientViewportWd:1920\r\n" +
"a=x-nv-video[0].clientViewportHt:1080\r\n" +
"a=x-nv-video[0].maxFPS:60\r\n" +
fmt.Sprintf("a=x-nv-video[0].clientViewportWd:%d\r\n", policy.ResolutionWidth) +
fmt.Sprintf("a=x-nv-video[0].clientViewportHt:%d\r\n", policy.ResolutionHeight) +
fmt.Sprintf("a=x-nv-video[0].maxFPS:%d\r\n", policy.Fps) +
"a=x-nv-video[0].packetSize:1024\r\n" +
"a=x-nv-video[0].videoEncoderSlicesPerFrame:1\r\n" +
"a=x-nv-video[0].maxNumReferenceFrames:0\r\n" +
"a=x-nv-vqos[0].bitStreamFormat:0\r\n" +
"a=x-nv-vqos[0].bw.maximumBitrateKbps:8000\r\n" +
fmt.Sprintf("a=x-nv-clientSupportHevc:%d\r\n", supportsHEVC) +
fmt.Sprintf("a=x-nv-vqos[0].bitStreamFormat:%d\r\n", format) +
fmt.Sprintf("a=x-nv-video[0].initialBitrateKbps:%d\r\n", maximumBitrate) +
fmt.Sprintf("a=x-nv-video[0].initialPeakBitrateKbps:%d\r\n", maximumBitrate) +
fmt.Sprintf("a=x-nv-vqos[0].bw.minimumBitrateKbps:%d\r\n", maximumBitrate) +
fmt.Sprintf("a=x-nv-vqos[0].bw.maximumBitrateKbps:%d\r\n", maximumBitrate) +
"a=x-nv-vqos[0].fec.minRequiredFecPackets:2\r\n" +
"a=x-nv-vqos[0].qosTrafficType:5\r\n" +
"a=x-nv-audio.surround.numChannels:2\r\n" +
@@ -490,10 +508,17 @@ func apolloAnnounceProfile() []byte {
"a=x-nv-general.useReliableUdp:13\r\n" +
"a=x-nv-general.featureFlags:167\r\n" +
"a=x-ml-general.featureFlags:0\r\n" +
"a=x-ml-video.configuredBitrateKbps:8000\r\n" +
fmt.Sprintf("a=x-ml-video.configuredBitrateKbps:%d\r\n", policy.BitrateKbps) +
"a=x-ss-general.encryptionEnabled:7\r\n" +
"a=x-ss-video[0].chromaSamplingType:0\r\n" +
"a=x-ss-video[0].intraRefresh:0\r\n")
"a=x-ss-video[0].intraRefresh:0\r\n"), nil
}
func validateApolloStreamPolicy(policy protocol.ProviderStreamPolicy) error {
if err := policy.Validate(); err != nil || !policy.AudioEnabled || (policy.Codec != "H264" && policy.Codec != "HEVC") {
return ErrProviderMalformed
}
return nil
}
func validApolloRTSPToken(value string) bool {
+3 -1
View File
@@ -8,6 +8,8 @@ import (
var ErrNoCapabilityOverlap = errors.New("no capability overlap")
const defaultClientDecode = "h264-hevc-opus"
func DefaultCapabilities() protocol.CapabilityProfile {
return protocol.CapabilityProfile{
Transport: "quic-tls13",
@@ -15,7 +17,7 @@ func DefaultCapabilities() protocol.CapabilityProfile {
Media: "encoded",
Audio: "encoded",
SourceRateControl: "server",
ClientDecode: "h264-opus",
ClientDecode: defaultClientDecode,
}
}
+11
View File
@@ -32,6 +32,17 @@ func TestFairPacerEightFlowSharesAndCapacitySteps(t *testing.T) {
assertSyntheticCap(t, half, 500_000)
}
func TestFairPacerBoundsCatchupAfterHostStall(t *testing.T) {
start := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)
pacer := newFairPacer(8000)
_ = pacer.reserveAt(start, "one", 1000)
resumed := start.Add(100 * time.Millisecond)
next := pacer.reserveAt(resumed, "one", 1000)
if next.Before(resumed.Add(-fairPacerMaximumCatchup)) || next.After(resumed.Add(10*time.Millisecond)) {
t.Fatalf("post-stall reservation = %s, want bounded catchup near %s", next, resumed)
}
}
func runSyntheticPacer(pacer *fairPacer, start, end time.Time, flows []string, next map[string]time.Time) []syntheticPacerDelivery {
const packetBytes = 1000
for _, flow := range flows {
+221 -7
View File
@@ -321,6 +321,56 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
}
}
func TestGatewayRejectsProviderWorkOutsideNegotiatedDecodeProfile(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
fake := NewFakeApollo(FakeApolloConfig{Now: time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)})
capabilities := DefaultCapabilities()
capabilities.ClientDecode = "h264-opus"
authority := protocol.SessionAuthority{
Version: "1", SessionID: "session-policy", GatewayID: "gateway-1", Audience: "versevdi-gateway",
ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano),
Capabilities: capabilities, ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key(),
}
admission := &oneTimeAdmission{
authority: authority, released: make(chan struct{}),
streamPolicy: protocol.ProviderStreamPolicy{
ResolutionWidth: 2560, ResolutionHeight: 1440, Fps: 120,
Codec: "HEVC", BitrateKbps: 40000, AudioEnabled: true,
},
}
server, err := NewServer(ServerConfig{
ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: "gateway-1",
Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: admission, Provider: fake,
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
serveDone := make(chan error, 1)
go func() { serveDone <- server.Serve(ctx) }()
request := protocol.TunnelAdmissionRequest{
Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience,
Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86),
Capabilities: capabilities,
}
if _, err := Dial(context.Background(), server.Addr().String(), clientTLS, request); err == nil {
t.Fatal("gateway accepted HEVC provider work for an H.264-only negotiated profile")
}
select {
case <-admission.released:
case <-time.After(time.Second):
t.Fatal("gateway did not release rejected provider work")
}
if session, _ := fake.LastSession().(*fakeSession); session != nil {
t.Fatal("provider started before policy/capability rejection")
}
_ = server.Close()
if err := <-serveDone; err != nil {
t.Fatal(err)
}
}
func TestRegisteredChannelFramesTraversePublicTransport(t *testing.T) {
h := newGatewayTransportHarness(t)
@@ -456,6 +506,116 @@ func TestProviderClipboardAuditWaitsForPublicTransportDelivery(t *testing.T) {
}
}
func TestProviderTerminationEndsPublicGatewaySession(t *testing.T) {
h := newGatewayTransportHarnessWithoutClipboard(t)
h.drainInitialMedia(t)
h.session.EmitEvent(ProviderEvent{Kind: ProviderEventTerminated, Payload: []byte{1, 2, 3, 4}})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
event, err := h.client.ReceiveProviderEvent(ctx)
cancel()
if err != nil || event.Kind != ProviderEventTerminated {
t.Fatalf("provider termination event = %#v, %v", event, err)
}
h.waitReleased(t)
if states := h.reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateTerminated {
t.Fatalf("provider states = %#v", states)
}
h.assertMediaClosed(t)
}
func TestEncryptedNativeHostTerminationEndsPublicGatewaySession(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
key := []byte("0123456789abcdef")
native := newNativeApolloSession("session-native-terminal")
control, err := newApolloControlCodec(key)
if err != nil {
t.Fatal(err)
}
native.control = control
close(native.readDone)
session := nativeLifecycleSession{native}
provider := providerStartFunc(func(context.Context, LaunchRequest) (ProviderSession, error) {
if err := native.Ready(context.Background()); err != nil {
return nil, err
}
return session, nil
})
authority := protocol.SessionAuthority{
Version: "1", SessionID: native.sessionID, GatewayID: "gateway-1", Audience: "versevdi-gateway",
ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano),
Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: "apollo-fixture-1#sha256:fixture-apollo-1",
}
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{}), disableClipboard: true}
reporter := &recordingProviderStateReporter{}
server, err := NewServer(ServerConfig{
ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID,
Capabilities: DefaultCapabilities(), ProviderCapabilities: DefaultCapabilities(),
Admission: admission, ProviderStateReporter: reporter, Provider: provider,
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
serveDone := make(chan error, 1)
go func() { serveDone <- server.Serve(ctx) }()
request := protocol.TunnelAdmissionRequest{
Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience,
Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86),
Capabilities: DefaultCapabilities(),
}
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
if err != nil {
t.Fatal(err)
}
native.handleApolloControlPayload(apolloChannelGeneric, true, sourceSealHostControl(t, key, 0, apolloControlTypeTerm, []byte{1, 2, 3, 4}))
eventCtx, eventCancel := context.WithTimeout(context.Background(), time.Second)
event, err := client.ReceiveProviderEvent(eventCtx)
eventCancel()
if err != nil || event.Kind != ProviderEventTerminated {
t.Fatalf("native provider termination = %#v, %v", event, err)
}
select {
case <-admission.released:
case <-time.After(2 * time.Second):
t.Fatal("native termination did not release admission")
}
if states := reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateTerminated {
t.Fatalf("provider states = %#v", states)
}
_ = client.Close()
_ = server.Close()
if err := <-serveDone; err != nil {
t.Fatal(err)
}
}
func TestProviderDisconnectEndsPublicGatewaySessionReconnectable(t *testing.T) {
h := newGatewayTransportHarnessWithoutClipboard(t)
h.drainInitialMedia(t)
h.session.Disconnect()
h.waitReleased(t)
if states := h.reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateDisconnected || states[len(states)-1].CleanupPending {
t.Fatalf("provider states = %#v", states)
}
h.assertMediaClosed(t)
}
func TestProviderTerminalCleanupFailureReportsCleanupPending(t *testing.T) {
h := newGatewayTransportHarnessWithoutClipboard(t)
h.drainInitialMedia(t)
h.session.failure = FakeFailureTerminationTimeout
h.session.EmitEvent(ProviderEvent{Kind: ProviderEventTerminated, Payload: []byte{1, 2, 3, 4}})
h.waitReleased(t)
if states := h.reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateCleanup || !states[len(states)-1].CleanupPending {
t.Fatalf("provider states = %#v", states)
}
h.assertMediaClosed(t)
}
func testChannelFrame(flowID string, sequence int64, payload []byte) protocol.ChannelFrame {
return protocol.ChannelFrame{Version: "1", FlowID: flowID, Sequence: sequence, Flags: 0, FragmentIndex: 0, FragmentCount: 1, TimestampMs: time.Now().UnixMilli(), Payload: base64.StdEncoding.EncodeToString(payload)}
}
@@ -468,12 +628,32 @@ type gatewayTransportHarness struct {
server *Server
}
type providerStartFunc func(context.Context, LaunchRequest) (ProviderSession, error)
func (fn providerStartFunc) Start(ctx context.Context, request LaunchRequest) (ProviderSession, error) {
return fn(ctx, request)
}
type nativeLifecycleSession struct{ *nativeApolloSession }
func (s nativeLifecycleSession) Telemetry() ProviderTelemetry {
return ProviderTelemetry{State: s.State().State}
}
func newGatewayTransportHarness(t *testing.T) gatewayTransportHarness {
return newGatewayTransportHarnessWithClipboard(t, true)
}
func newGatewayTransportHarnessWithoutClipboard(t *testing.T) gatewayTransportHarness {
return newGatewayTransportHarnessWithClipboard(t, false)
}
func newGatewayTransportHarnessWithClipboard(t *testing.T, clipboardEnabled bool) gatewayTransportHarness {
t.Helper()
serverTLS, clientTLS := testTLS(t)
fake := NewFakeApollo(FakeApolloConfig{Now: time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)})
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-transport", GatewayID: "gateway-1", Audience: "versevdi-gateway", ReconnectSequence: 0, ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key()}
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{})}
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{}), disableClipboard: !clipboardEnabled}
reporter := &recordingProviderStateReporter{}
server, err := NewServer(ServerConfig{ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: "gateway-1", Capabilities: DefaultCapabilities(), ProviderCapabilities: DefaultCapabilities(), Admission: admission, ProviderStateReporter: reporter, ClipboardAuditReporter: reporter, Provider: fake})
if err != nil {
@@ -513,6 +693,28 @@ func (h gatewayTransportHarness) waitReleased(t *testing.T) {
}
}
func (h gatewayTransportHarness) drainInitialMedia(t *testing.T) {
t.Helper()
for range 2 {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err := h.client.ReceiveFrame(ctx)
cancel()
if err != nil {
t.Fatalf("drain initial media: %v", err)
}
}
}
func (h gatewayTransportHarness) assertMediaClosed(t *testing.T) {
t.Helper()
h.session.EmitVideo([]byte("must-not-forward"))
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if frame, err := h.client.ReceiveFrame(ctx); err == nil {
t.Fatalf("media remained open after provider terminal state: %#v", frame)
}
}
func testTLS(t *testing.T) (*tls.Config, *tls.Config) {
t.Helper()
caKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -545,10 +747,12 @@ func testTLS(t *testing.T) (*tls.Config, *tls.Config) {
}
type oneTimeAdmission struct {
used atomic.Bool
authority protocol.SessionAuthority
releases atomic.Int64
released chan struct{}
used atomic.Bool
authority protocol.SessionAuthority
releases atomic.Int64
released chan struct{}
streamPolicy protocol.ProviderStreamPolicy
disableClipboard bool
}
type recordingProviderStateReporter struct {
@@ -594,14 +798,24 @@ func (a *oneTimeAdmission) ProviderWork(_ context.Context, authority protocol.Se
if authority != a.authority {
return protocol.ProviderSessionWork{}, ErrAdmissionRejected
}
streamPolicy := a.streamPolicy
if streamPolicy == (protocol.ProviderStreamPolicy{}) {
streamPolicy = protocol.ProviderStreamPolicy{ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 8000, AudioEnabled: true}
}
clipboardPolicy := protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30}
if !a.disableClipboard {
clipboardPolicy.ClientToProviderEnabled = true
clipboardPolicy.ProviderToClientEnabled = true
}
return protocol.ProviderSessionWork{
Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID,
ReconnectSequence: authority.ReconnectSequence, ExpiresAt: authority.ExpiresAt,
ProviderProfile: ProviderProfileApollo, ProviderIdentity: authority.ProviderIdentity,
PolicyVersionID: "policy-1", ApplicationID: "1", ClientID: "paired-client", ManagementHost: "apollo.test", ManagementPort: 47990,
StreamHost: "apollo.test", StreamPort: 47984, ClientCertificatePem: "certificate",
StreamPolicy: streamPolicy,
StreamHost: "apollo.test", StreamPort: 47984, ClientCertificatePem: "certificate",
ClientPrivateKeyPem: "private-key", ServerCertificatePem: "server-certificate",
ClipboardPolicy: protocol.ClipboardPolicy{ClientToProviderEnabled: true, ProviderToClientEnabled: true, MaxTextBytes: 65536, MaxUpdatesPerMinute: 30},
ClipboardPolicy: clipboardPolicy,
}, nil
}
+8 -1
View File
@@ -186,6 +186,7 @@ const (
ProviderEventTerminated ProviderEventKind = iota + 1
ProviderEventRumble
ProviderEventHDR
ProviderEventDisconnected
)
type ProviderEvent struct {
@@ -563,12 +564,17 @@ func (s *fakeSession) Terminate(ctx context.Context) error {
s.mu.Unlock()
return nil
}
disconnected := s.state.State == ProviderStateDisconnected
s.state.State = ProviderStateTerminating
s.closeOnce.Do(func() {
close(s.video)
close(s.audio)
})
s.state.State = ProviderStateTerminated
if disconnected {
s.state.State = ProviderStateDisconnected
} else {
s.state.State = ProviderStateTerminated
}
s.mu.Unlock()
return nil
}
@@ -587,6 +593,7 @@ func (s *fakeSession) Disconnect() {
s.mu.Lock()
s.state.State = ProviderStateDisconnected
s.mu.Unlock()
s.EmitEvent(ProviderEvent{Kind: ProviderEventDisconnected})
}
func (s *fakeSession) ReleaseCount() int {
+60 -18
View File
@@ -54,6 +54,14 @@ func TestQualificationProtocolVersionIsExplicitAndImmutable(t *testing.T) {
}
}
func TestQualificationRecordsLinkedToolVersions(t *testing.T) {
versions, err := qualificationToolVersions()
if err != nil || versions["qualification"] != qualificationToolVersion ||
versions["go"] == "" || versions["quic-go"] == "" {
t.Fatalf("qualification tool versions = %#v, %v", versions, err)
}
}
func TestQualificationOutputAndStatisticsFailClosed(t *testing.T) {
if err := validateQualificationOutputDir("relative/evidence"); err == nil {
t.Fatal("relative evidence directory was accepted")
@@ -86,15 +94,16 @@ func TestQualificationOutputAndStatisticsFailClosed(t *testing.T) {
func TestQualificationShortProcessingWritesRawArtifact(t *testing.T) {
profile := qualificationMediaProfile{
Name: "smoke", Codec: "h264", BitrateKbps: 1000,
Duration: 200 * time.Millisecond, Warmup: time.Millisecond, PacketBytes: 100,
Name: "smoke", Codec: "h264", BitrateKbps: 20000,
Duration: time.Second, Warmup: time.Millisecond, PacketBytes: 1000,
}
rawPath := filepath.Join(t.TempDir(), "processing.csv.gz")
summary, err := runQualificationProcessing(profile, rawPath)
summary, err := runQualificationProcessing(t, profile, rawPath)
if err != nil {
t.Fatal(err)
}
if summary.Count < 1 || summary.RawSamplesSHA256 == "" || summary.RawSamplesBytes < 1 {
if summary.Count < 1 || summary.RawSamplesSHA256 == "" || summary.RawSamplesBytes < 1 ||
summary.ResourceSamples < 2 || summary.RawResourcesSHA256 == "" || summary.RawResourcesBytes < 1 {
t.Fatalf("processing summary = %#v", summary)
}
file, err := os.Open(rawPath)
@@ -119,13 +128,14 @@ func TestQualificationShortProcessingWritesRawArtifact(t *testing.T) {
}
func TestQualificationProcessingPreservesPayload(t *testing.T) {
payload := qualificationPayload(qualificationMediaProfiles()[0])
processed, elapsed, err := processQualificationPayload(7, payload)
profile := qualificationMediaProfiles()[0]
payload := qualificationPayload(profile)
trace, elapsed, err := newQualificationPath(t, profile.BitrateKbps).traverse(t, payload)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(processed, payload) {
t.Fatal("encoded payload mutated")
if !trace.PayloadPreserved || !trace.ApolloRecovered || !trace.VerseQUIC {
t.Fatalf("production path trace = %#v", trace)
}
if elapsed <= 0 {
t.Fatalf("processing duration = %s", elapsed)
@@ -134,34 +144,66 @@ func TestQualificationProcessingPreservesPayload(t *testing.T) {
func TestQualificationImpairmentIsDeterministicAndBounded(t *testing.T) {
profile := qualificationImpairmentProfiles()[3]
first, err := runQualificationImpairment(profile, qualificationMediaProfiles()[0], 10_000)
first, err := runQualificationImpairment(t, profile, qualificationMediaProfiles()[0], 1000, filepath.Join(t.TempDir(), "first.csv.gz"))
if err != nil {
t.Fatal(err)
}
second, err := runQualificationImpairment(profile, qualificationMediaProfiles()[0], 10_000)
second, err := runQualificationImpairment(t, profile, qualificationMediaProfiles()[0], 1000, filepath.Join(t.TempDir(), "second.csv.gz"))
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(first, second) {
t.Fatalf("impairment run is not deterministic:\n%#v\n%#v", first, second)
if first.Dropped != second.Dropped || first.InjectedReordered != second.InjectedReordered {
t.Fatalf("deterministic impairment selection differs:\n%#v\n%#v", first, second)
}
if first.Sent != 10_000 || first.Delivered+first.Dropped != first.Sent ||
first.ObservedLossPercent < 4.8 || first.ObservedLossPercent > 5.2 ||
first.MaxQueuePackets > qualificationImpairmentQueuePackets {
if first.Sent != 1000 || first.Delivered+first.Dropped != first.Sent ||
first.ObservedLossPercent < 3.5 || first.ObservedLossPercent > 6.5 ||
first.MaxQueuePackets > qualificationImpairmentQueuePackets || first.RawSamplesSHA256 == "" {
t.Fatalf("impairment observation = %#v", first)
}
if _, err := runQualificationImpairment(profile, qualificationMediaProfiles()[0], qualificationImpairmentMaxPackets+1); err == nil {
if _, err := runQualificationImpairment(t, profile, qualificationMediaProfiles()[0], qualificationImpairmentMaxPackets+1, filepath.Join(t.TempDir(), "invalid.csv.gz")); err == nil {
t.Fatal("unbounded impairment packet count was accepted")
}
unknown := profile
unknown.Name = "private-simulator"
if _, err := runQualificationImpairment(t, unknown, qualificationMediaProfiles()[0], 1, filepath.Join(t.TempDir(), "unknown.csv.gz")); err == nil {
t.Fatal("unregistered impairment profile was accepted")
}
}
func TestQualificationSixImpairmentProfilesTraverseProductionPath(t *testing.T) {
profiles := qualificationImpairmentProfiles()
if len(profiles) != 6 {
t.Fatalf("impairment profile count = %d, want exactly 6", len(profiles))
}
for _, profile := range profiles {
observation, err := runQualificationImpairment(t, profile, qualificationMediaProfiles()[0], 40,
filepath.Join(t.TempDir(), profile.Name+".csv.gz"))
if err != nil {
t.Fatalf("%s: %v", profile.Name, err)
}
if observation.Profile != profile.Name || observation.Delivered+observation.Dropped != 40 ||
observation.RawSamplesSHA256 == "" || observation.MaxQueuePackets > qualificationImpairmentQueuePackets {
t.Fatalf("%s observation = %#v", profile.Name, observation)
}
}
}
func TestQualificationUsesPublicQUICAndProductionPacer(t *testing.T) {
qualificationTraverseProfiles(t, qualificationMediaProfiles())
evidence, err := qualificationPacerEvidence()
evidence, err := qualificationPacerEvidence(filepath.Join(t.TempDir(), "fairness.csv.gz"))
if err != nil {
t.Fatal(err)
}
if len(evidence.PerFlowBytes) != 8 || len(evidence.CapacitySteps) != 2 || evidence.JainIndex < 0.99 {
if len(evidence.PerFlowBytes) != 8 || len(evidence.CapacitySteps) != 2 ||
len(evidence.Series) != 80 || evidence.RawSamplesSHA256 == "" || evidence.JainIndex < 0.99 {
t.Fatalf("pacer evidence = %#v", evidence)
}
}
func TestQualificationSmokeTraversesNativeApolloRecoveryQueuePacerAndQUIC(t *testing.T) {
trace := qualificationProductionPathSmoke(t, qualificationMediaProfiles()[0])
if !trace.ApolloRecovered || !trace.ProductionQueue || !trace.ProductionPacer ||
!trace.VerseQUIC || !trace.PayloadPreserved {
t.Fatalf("qualification production-path trace = %#v", trace)
}
}
File diff suppressed because it is too large Load Diff
+7 -3
View File
@@ -132,6 +132,8 @@ type fairPacerFlow struct {
lastSeen time.Time
}
const fairPacerMaximumCatchup = 5 * time.Millisecond
func newFairPacer(kbps int64) *fairPacer {
pacer := &fairPacer{flows: make(map[string]fairPacerFlow)}
pacer.setKbps(kbps)
@@ -177,9 +179,11 @@ func (p *fairPacer) reserveAt(now time.Time, flow string, bytes int) time.Time {
state := p.flows[flow]
state.lastSeen = now
p.flows[flow] = state
base := now
if state.next.After(base) {
base = state.next
base := state.next
if base.IsZero() {
base = now
} else if lag := now.Sub(base); lag > fairPacerMaximumCatchup {
base = now.Add(-fairPacerMaximumCatchup)
}
numerator := int64(bytes) * int64(len(p.flows)) * int64(time.Second)
delay := time.Duration((numerator + p.bytesPerSecond - 1) / p.bytesPerSecond)
+44 -9
View File
@@ -19,13 +19,14 @@ import (
)
const (
defaultHelloLimit = 16 * 1024
defaultControlLimit = 128 * 1024
clientControlBacklog = 64
applicationError = quic.ApplicationErrorCode(0x100)
controlFlowID = "control.ack.v1"
inputFlowID = "input.sequenced.v1"
clipboardFlowID = "clipboard.text.v1"
defaultHelloLimit = 16 * 1024
defaultControlLimit = 128 * 1024
clientControlBacklog = 64
applicationError = quic.ApplicationErrorCode(0x100)
terminalFeedbackDrain = 100 * time.Millisecond
controlFlowID = "control.ack.v1"
inputFlowID = "input.sequenced.v1"
clipboardFlowID = "clipboard.text.v1"
)
var (
@@ -324,12 +325,26 @@ func (s *Server) validateProviderWork(work protocol.ProviderSessionWork, authori
}
if work.SessionID != authority.SessionID || work.GatewayID != authority.GatewayID ||
work.ReconnectSequence != authority.ReconnectSequence || work.ExpiresAt != authority.ExpiresAt ||
work.ProviderProfile != authority.ProviderProfile {
work.ProviderProfile != authority.ProviderProfile || !apolloPolicyMatchesCapabilities(work.StreamPolicy, authority.Capabilities) {
return ErrAdmissionRejected
}
return nil
}
func apolloPolicyMatchesCapabilities(policy protocol.ProviderStreamPolicy, capabilities protocol.CapabilityProfile) bool {
if validateApolloStreamPolicy(policy) != nil || capabilities.Audio != "encoded" {
return false
}
switch policy.Codec {
case "H264":
return capabilities.ClientDecode == "h264-opus" || capabilities.ClientDecode == defaultClientDecode
case "HEVC":
return capabilities.ClientDecode == "hevc-opus" || capabilities.ClientDecode == defaultClientDecode
default:
return false
}
}
func (s *Server) addSession(session *gatewaySession) {
s.mu.Lock()
s.sessions[session] = struct{}{}
@@ -358,6 +373,7 @@ type gatewaySession struct {
pressed map[string]struct{}
sequence atomic.Uint32
mediaDrops uint64
endReason error
result chan error
}
@@ -389,7 +405,7 @@ func (s *gatewaySession) run() {
case <-timer.C:
s.server.metrics.InputRejected.Add(1)
case <-s.ctx.Done():
case <-s.result:
case s.endReason = <-s.result:
}
s.cancel()
}
@@ -404,6 +420,10 @@ func (s *gatewaySession) providerEventLoop() {
if !ok {
return
}
if event.Kind == ProviderEventDisconnected {
s.result <- ErrProviderDisconnected
return
}
payload, err := EncodeProviderEvent(event)
if err == nil {
err = s.sendControl(s.sequence.Add(1), payload)
@@ -412,6 +432,17 @@ func (s *gatewaySession) providerEventLoop() {
s.result <- err
return
}
if event.Kind == ProviderEventTerminated {
timer := time.NewTimer(terminalFeedbackDrain)
select {
case <-s.ctx.Done():
timer.Stop()
return
case <-timer.C:
}
s.result <- ErrProviderTerminated
return
}
}
}
}
@@ -771,6 +802,10 @@ func (s *gatewaySession) cleanup() {
_ = s.connection.CloseWithError(applicationError, "session closed")
return
}
if errors.Is(s.endReason, ErrProviderDisconnected) {
state.State = ProviderStateDisconnected
state.CleanupPending = false
}
if err := s.server.config.Admission.Release(cleanupCtx, s.authority); err != nil {
s.server.metrics.ProviderErrors.Add(1)
}