feat(gateway): repair native Apollo provider path
This commit is contained in:
+89
-44
@@ -55,24 +55,49 @@ func FuzzDecodeFrame(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDecodeControlPacket(f *testing.F) {
|
||||
seed, _ := EncodeControlPacket(ControlPacket{Kind: 1, Sequence: 2, Payload: []byte("fixture")})
|
||||
f.Add(seed)
|
||||
f.Add([]byte("APC1"))
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
_, _ = DecodeControlPacket(data)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDecodeInputEvent(f *testing.F) {
|
||||
seed, _ := EncodeInputEvent(InputEvent{Sequence: 1, Device: "keyboard", Code: 7, Pressed: true})
|
||||
f.Add(seed)
|
||||
f.Add([]byte("INP1"))
|
||||
f.Add([]byte("VGI1"))
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
_, _ = DecodeInputEvent(data)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInputEventUsesFixedProtocolVGI1Vector(t *testing.T) {
|
||||
encoded, err := EncodeInputEvent(InputEvent{Sequence: 7, Device: "keyboard", Code: 30, Pressed: true, Payload: []byte{2}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const expected = "5647493101040102001e"
|
||||
if hex.EncodeToString(encoded) != expected {
|
||||
t.Fatalf("EncodeInputEvent() = %x, want %s", encoded, expected)
|
||||
}
|
||||
decoded, err := DecodeInputEvent(encoded)
|
||||
if err != nil || decoded.Sequence != 0 || decoded.Device != "keyboard" || decoded.Code != 30 || !decoded.Pressed || string(decoded.Payload) != string([]byte{2}) {
|
||||
t.Fatalf("DecodeInputEvent() = %#v, %v", decoded, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientFeedbackUsesFixedProtocolVGFVector(t *testing.T) {
|
||||
feedback := Feedback{Sequence: 9, Kind: FeedbackFEC, Payload: []byte{0, 0, 0, 42, 0, 5, 0, 3, 0, 2, 0, 10, 0, 2, 0, 8, 0, 2, 20, 0, 1}}
|
||||
encoded, err := EncodeClientFeedback(feedback)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const expected = "56474631000200150000002a000500030002000a000200080002140001"
|
||||
if hex.EncodeToString(encoded) != expected {
|
||||
t.Fatalf("EncodeClientFeedback() = %x, want %s", encoded, expected)
|
||||
}
|
||||
decoded, err := DecodeClientFeedback(encoded)
|
||||
if err != nil || decoded.Kind != FeedbackFEC || string(decoded.Payload) != string(feedback.Payload) {
|
||||
t.Fatalf("DecodeClientFeedback() = %#v, %v", decoded, err)
|
||||
}
|
||||
if _, err := DecodeClientFeedback([]byte{'F', 'B', 'R', 'K', 0}); !errors.Is(err, ErrProviderMalformed) {
|
||||
t.Fatalf("legacy feedback accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapabilityIntersectionAndBoundedQueue(t *testing.T) {
|
||||
capabilities := DefaultCapabilities()
|
||||
if _, err := IntersectCapabilities(capabilities, capabilities); err != nil {
|
||||
@@ -199,39 +224,6 @@ func TestProviderTimeoutAndBoundedInput(t *testing.T) {
|
||||
if _, err := EncodeInputEvent(InputEvent{Device: strings.Repeat("d", 65)}); !errors.Is(err, ErrInputMalformed) {
|
||||
t.Fatalf("oversized input accepted: %v", err)
|
||||
}
|
||||
if _, err := DecodeControlPacket([]byte("APC1")); !errors.Is(err, ErrProviderMalformed) {
|
||||
t.Fatalf("truncated control accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeApolloEncodedRelay(t *testing.T) {
|
||||
provider, peer := net.Pipe()
|
||||
session := newNativeApolloSession(provider, "session-native")
|
||||
go session.readMedia()
|
||||
go func() {
|
||||
_, _ = peer.Write([]byte{'$', 0, 0, 3, 1, 2, 3})
|
||||
_, _ = peer.Write([]byte{'$', 1, 0, 2, 4, 5})
|
||||
}()
|
||||
select {
|
||||
case payload := <-session.Video():
|
||||
if string(payload) != string([]byte{1, 2, 3}) {
|
||||
t.Fatalf("video payload changed: %x", payload)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("video payload not relayed")
|
||||
}
|
||||
select {
|
||||
case payload := <-session.Audio():
|
||||
if string(payload) != string([]byte{4, 5}) {
|
||||
t.Fatalf("audio payload changed: %x", payload)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("audio payload not relayed")
|
||||
}
|
||||
terminateCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||||
defer cancel()
|
||||
_ = session.Terminate(terminateCtx)
|
||||
_ = peer.Close()
|
||||
}
|
||||
|
||||
func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
|
||||
@@ -240,7 +232,7 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
|
||||
authority := protocol.SessionAuthority{Version: "1", SessionID: "session-1", 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{})}
|
||||
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, Provider: fake})
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -263,6 +255,44 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
|
||||
t.Fatalf("unexpected media channel %d", frame.Channel)
|
||||
}
|
||||
}
|
||||
metrics := server.Metrics()
|
||||
if metrics.AdmittedSessions != 1 || metrics.MediaPackets < 2 || metrics.MediaBytes == 0 || metrics.ProcessingSamples < 2 || metrics.ProviderState != 2 {
|
||||
t.Fatalf("observed egress telemetry = %#v", metrics)
|
||||
}
|
||||
fakeSession, ok := fake.LastSession().(*fakeSession)
|
||||
if !ok {
|
||||
t.Fatal("fake provider session type")
|
||||
}
|
||||
fakeSession.mu.Lock()
|
||||
fakeSession.clipboard = "host clipboard"
|
||||
fakeSession.mu.Unlock()
|
||||
fakeSession.EmitEvent(ProviderEvent{Kind: ProviderEventRumble, Payload: []byte{1, 0x12, 0x34, 0x56, 0x78}})
|
||||
clipboardCtx, clipboardCancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
deliveredClipboard, clipboardErr := client.ReceiveClipboard(clipboardCtx)
|
||||
clipboardCancel()
|
||||
if clipboardErr != nil || deliveredClipboard.Direction != "provider_to_client" || deliveredClipboard.Text != "host clipboard" || deliveredClipboard.Encoding != "utf-8" {
|
||||
t.Fatalf("provider clipboard = %#v, %v", deliveredClipboard, clipboardErr)
|
||||
}
|
||||
if audits := reporter.Audits(); len(audits) == 0 || audits[0].Direction != "provider_to_client" || audits[0].Outcome != "forwarded" || audits[0].Reason != "forwarded" || audits[0].TextBytes != int64(len("host clipboard")) {
|
||||
t.Fatalf("clipboard audits = %#v", audits)
|
||||
}
|
||||
eventCtx, eventCancel := context.WithTimeout(context.Background(), time.Second)
|
||||
event, eventErr := client.ReceiveProviderEvent(eventCtx)
|
||||
eventCancel()
|
||||
if eventErr != nil || event.Kind != ProviderEventRumble || string(event.Payload) != string([]byte{1, 0x12, 0x34, 0x56, 0x78}) {
|
||||
t.Fatalf("provider event = %#v, %v", event, eventErr)
|
||||
}
|
||||
if err := client.SendClipboard(protocol.GatewayClipboardText{Direction: "client_to_provider", Text: "clipboard", Encoding: "utf-8", LoopToken: "abcdefghijklmnop"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case value := <-fakeSession.clipboardWrites:
|
||||
if value != "clipboard" {
|
||||
t.Fatalf("provider clipboard = %q", value)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("gateway did not forward clipboard")
|
||||
}
|
||||
if err := client.SendInput(InputEvent{Sequence: 1, Device: "keyboard", Code: 7, Pressed: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -331,6 +361,7 @@ type oneTimeAdmission struct {
|
||||
type recordingProviderStateReporter struct {
|
||||
mu sync.Mutex
|
||||
states []protocol.ProviderState
|
||||
audits []protocol.GatewayClipboardAudit
|
||||
}
|
||||
|
||||
func (r *recordingProviderStateReporter) ReportProviderState(_ context.Context, state protocol.ProviderState) error {
|
||||
@@ -346,6 +377,19 @@ func (r *recordingProviderStateReporter) States() []protocol.ProviderState {
|
||||
return append([]protocol.ProviderState(nil), r.states...)
|
||||
}
|
||||
|
||||
func (r *recordingProviderStateReporter) ReportClipboardAudit(_ context.Context, audit protocol.GatewayClipboardAudit) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.audits = append(r.audits, audit)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *recordingProviderStateReporter) Audits() []protocol.GatewayClipboardAudit {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return append([]protocol.GatewayClipboardAudit(nil), r.audits...)
|
||||
}
|
||||
|
||||
func (a *oneTimeAdmission) Admit(context.Context, protocol.TunnelAdmissionRequest) (protocol.SessionAuthority, error) {
|
||||
if !a.used.CompareAndSwap(false, true) {
|
||||
return protocol.SessionAuthority{}, ErrAdmissionRejected
|
||||
@@ -364,6 +408,7 @@ func (a *oneTimeAdmission) ProviderWork(_ context.Context, authority protocol.Se
|
||||
PolicyVersionID: "policy-1", ApplicationID: "1", ClientID: "paired-client", ManagementHost: "apollo.test", ManagementPort: 47990,
|
||||
StreamHost: "apollo.test", StreamPort: 47984, ClientCertificatePem: "certificate",
|
||||
ClientPrivateKeyPem: "private-key", ServerCertificatePem: "server-certificate",
|
||||
ClipboardPolicy: protocol.ClipboardPolicy{ClientToProviderEnabled: true, ProviderToClientEnabled: true, MaxTextBytes: 65536, MaxUpdatesPerMinute: 30},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user