diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go index d58043d..f95866f 100644 --- a/gateway/gateway_test.go +++ b/gateway/gateway_test.go @@ -12,6 +12,7 @@ import ( "encoding/base64" "encoding/binary" "encoding/hex" + "encoding/json" "errors" "io" "math/big" @@ -969,10 +970,61 @@ func newNativeGatewayLifecycleHarness(t *testing.T, sessionID string) nativeGate return nativeGatewayLifecycleHarness{native: native, key: key, client: client, admission: admission, reporter: reporter} } +func TestGatewayEgressReturnsProviderFreeClientAuthority(t *testing.T) { + h := newNativeGatewayLifecycleHarness(t, "session-client-authority") + var got map[string]any + if err := json.Unmarshal(h.client.authorityRaw, &got); err != nil { + t.Fatal(err) + } + want := map[string]any{ + "version": "1", + "session_id": "session-client-authority", + "gateway_id": "gateway-1", + "audience": "versevdi-gateway", + "reconnect_sequence": float64(0), + "expires_at": h.admission.authority.ExpiresAt, + "capabilities": map[string]any{ + "transport": "quic-tls13", + "framing": "datagram-v2", + "media": "encoded", + "audio": "encoded", + "source_rate_control": "server", + "client_decode": []any{"h264-opus"}, + }, + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("raw client authority = %s", h.client.authorityRaw) + } + if err := h.client.Close(); err != nil { + t.Fatal(err) + } + h.waitReleased(t) + if !reflect.DeepEqual(h.admission.releaseAuthority, h.admission.authority) { + t.Fatalf("release authority = %#v, want provider-bearing %#v", h.admission.releaseAuthority, h.admission.authority) + } +} + +func TestClientSessionAuthorityRejectsProviderBearingRC3(t *testing.T) { + var _ protocol.ClientSessionAuthority = Client{}.Authority + raw, err := protocol.EncodeSessionAuthority(protocol.SessionAuthority{ + Version: "1", SessionID: "session-rc3", GatewayID: "gateway-1", Audience: "versevdi-gateway", + ExpiresAt: time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano), Capabilities: DefaultCapabilities(), + ProviderProfile: ProviderProfileApollo, ProviderIdentity: "apollo-fixture-1#sha256:fixture-apollo-1", + }) + if err != nil { + t.Fatal(err) + } + if _, err := protocol.DecodeClientSessionAuthority(raw); err == nil { + t.Fatalf("accepted provider-bearing RC3 authority: %s", raw) + } +} + type independentGatewayClient struct { - connection *quic.Conn - control *quic.Stream - media independentMediaReassembler + connection *quic.Conn + control *quic.Stream + authority protocol.ClientSessionAuthority + authorityRaw []byte + media independentMediaReassembler } func dialIndependentGateway(ctx context.Context, address string, tlsConfig *tls.Config, request protocol.TunnelAdmissionRequest) (*independentGatewayClient, error) { @@ -994,17 +1046,20 @@ func dialIndependentGateway(ctx context.Context, address string, tlsConfig *tls. if err == nil { encoded, err = independentReadWire(stream, defaultHelloLimit) } + var authority protocol.ClientSessionAuthority if err == nil { - _, err = protocol.DecodeSessionAuthority(encoded) + authority, err = protocol.DecodeClientSessionAuthority(encoded) } if err != nil { _ = connection.CloseWithError(applicationError, "independent client admission failed") return nil, err } return &independentGatewayClient{ - connection: connection, - control: stream, - media: independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)}, + connection: connection, + control: stream, + authority: authority, + authorityRaw: encoded, + media: independentMediaReassembler{incomplete: make(map[independentMediaKey]*independentMediaUnit)}, }, nil } @@ -1529,6 +1584,7 @@ func testTLS(t *testing.T) (*tls.Config, *tls.Config) { type oneTimeAdmission struct { used atomic.Bool authority protocol.SessionAuthority + releaseAuthority protocol.SessionAuthority releases atomic.Int64 released chan struct{} streamPolicy protocol.ProviderStreamPolicy @@ -1603,8 +1659,9 @@ func (a *oneTimeAdmission) ProviderWork(_ context.Context, authority protocol.Se }, nil } -func (a *oneTimeAdmission) Release(context.Context, protocol.SessionAuthority) error { +func (a *oneTimeAdmission) Release(_ context.Context, authority protocol.SessionAuthority) error { if a.releases.Add(1) == 1 { + a.releaseAuthority = authority close(a.released) } return nil diff --git a/gateway/transport.go b/gateway/transport.go index 0a66695..6cb1a1e 100644 --- a/gateway/transport.go +++ b/gateway/transport.go @@ -291,8 +291,11 @@ func (s *Server) handleConnection(parent context.Context, connection *quic.Conn) _ = writeStableError(stream, "provider_state_unavailable", err, true) return } - authority.Capabilities = selected - authorityBytes, err := protocol.EncodeSessionAuthority(authority) + clientAuthority := protocol.ClientSessionAuthority{ + Version: authority.Version, SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience, + ReconnectSequence: authority.ReconnectSequence, ExpiresAt: authority.ExpiresAt, Capabilities: selected, + } + authorityBytes, err := protocol.EncodeClientSessionAuthority(clientAuthority) if err != nil || writeWire(stream, authorityBytes, defaultHelloLimit) != nil { _ = providerSession.ReleaseAll(context.Background()) _ = providerSession.Terminate(context.Background()) @@ -1010,7 +1013,7 @@ type Client struct { controlReadMu sync.Mutex controlWriteMu sync.Mutex pendingControl map[string][][]byte - Authority protocol.SessionAuthority + Authority protocol.ClientSessionAuthority } func Dial(ctx context.Context, address string, tlsConfig *tls.Config, request protocol.TunnelAdmissionRequest) (*Client, error) { @@ -1045,7 +1048,7 @@ func Dial(ctx context.Context, address string, tlsConfig *tls.Config, request pr _ = connection.CloseWithError(applicationError, "no authority") return nil, err } - authority, authorityErr := protocol.DecodeSessionAuthority(response) + authority, authorityErr := protocol.DecodeClientSessionAuthority(response) if authorityErr != nil { stable, stableErr := protocol.DecodeStableError(response) if stableErr == nil { diff --git a/go.mod b/go.mod index a825bb4..d2e2a6b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane go 1.26.5 require ( - git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.3 + git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.5 github.com/quic-go/quic-go v0.61.0 ) diff --git a/go.sum b/go.sum index dff4d8c..a933ec0 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.2 h1 git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.2/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc= git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.3 h1:43EJHiKcbWdF0K1dvVznpWaUUWcvT5nogcGiSnOVu5s= git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.3/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc= +git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.5 h1:bJ3JeCm7dsQwWc7kMjcSgBrtATT1hi2xUSNdE73q4X4= +git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.5/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=