package gateway import ( "bytes" "context" "crypto/aes" "crypto/cipher" "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/binary" "encoding/hex" "encoding/pem" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "strconv" "strings" "testing" "time" protocol "git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol/gen/go/protocol" ) type nativeRoundTripFunc func(*http.Request) (*http.Response, error) func (fn nativeRoundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) { return fn(request) } func TestNativeApolloManagementUsesSessionScopedMTLS(t *testing.T) { serverTLS, clientTLS := testTLS(t) server := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.URL.Path != "/serverinfo" || request.TLS == nil || len(request.TLS.PeerCertificates) != 1 { http.Error(response, "mTLS required", http.StatusUnauthorized) return } _, _ = response.Write([]byte("apollo-server")) })) server.TLS = serverTLS server.StartTLS() defer server.Close() host, portText, err := net.SplitHostPort(server.Listener.Addr().String()) if err != nil { t.Fatal(err) } port, err := strconv.ParseInt(portText, 10, 64) if err != nil { t.Fatal(err) } pinned := sha256.Sum256(serverTLS.Certificates[0].Certificate[0]) work := protocol.ProviderSessionWork{ 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]), ServerCertificatePem: certificatePEM(t, tls.Certificate{Certificate: [][]byte{serverTLS.Certificates[0].Certificate[1]}}), ClipboardPolicy: protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30}, } data, err := NewNativeApolloBackend().Management(context.Background(), LaunchRequest{SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work}) if err != nil { t.Fatalf("Management() error = %v", err) } if info, err := ParseManagementXML(data); err != nil || info.Identity.UniqueID != "apollo-server" { t.Fatalf("ParseManagementXML() = %+v, %v", info, err) } } 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}, "h264-resolution": {ResolutionWidth: 4097, ResolutionHeight: 2160, Fps: 60, Codec: "H264", BitrateKbps: 50000, AudioEnabled: true}, "hevc-resolution": {ResolutionWidth: 8193, ResolutionHeight: 4320, Fps: 60, Codec: "HEVC", BitrateKbps: 80000, AudioEnabled: true}, "fps": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 241, Codec: "H264", BitrateKbps: 8000, AudioEnabled: true}, "bitrate-cap": {ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 125001, 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, }, nil) if !errors.Is(err, ErrProviderMalformed) { t.Fatalf("Setup() error = %v, want ErrProviderMalformed before provider readiness", err) } }) } } func TestNativeApolloRejectsProviderCapabilityMismatchBeforeInventoryOrLaunch(t *testing.T) { serverTLS, clientTLS := testTLS(t) var paths []string management := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { paths = append(paths, request.URL.Path) switch request.URL.Path { case "/serverinfo": _, _ = response.Write([]byte("apollo-server10")) case "/applist": _, _ = response.Write([]byte("42")) default: http.Error(response, "unexpected provider request", http.StatusBadRequest) } })) management.TLS = serverTLS management.StartTLS() defer management.Close() host, portText, err := net.SplitHostPort(management.Listener.Addr().String()) if err != nil { t.Fatal(err) } port, err := strconv.ParseInt(portText, 10, 64) if err != nil { t.Fatal(err) } pinned := sha256.Sum256(serverTLS.Certificates[0].Certificate[0]) work := protocol.ProviderSessionWork{ Version: "1", SessionID: "session-source-policy", GatewayID: "gateway-1", 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: host, ManagementPort: port, StreamHost: host, StreamPort: 47984, ClientCertificatePem: certificatePEM(t, clientTLS.Certificates[0]), ClientPrivateKeyPem: privateKeyPEM(t, clientTLS.Certificates[0]), ServerCertificatePem: certificatePEM(t, tls.Certificate{Certificate: [][]byte{serverTLS.Certificates[0].Certificate[1]}}), ClipboardPolicy: protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30}, } adapter := NewApolloAdapter(NewNativeApolloBackend(), ProviderIdentity{UniqueID: "apollo-server", Fingerprint: "sha256:" + hex.EncodeToString(pinned[:])}) if _, err := adapter.Start(context.Background(), LaunchRequest{ SessionID: work.SessionID, ProviderProfile: ProviderProfileApollo, ProviderIdentity: work.ProviderIdentity, ProviderWork: work, }); err == nil { t.Fatal("Start() accepted a provider/source policy mismatch") } if got := strings.Join(paths, ","); got != "/serverinfo" { t.Fatalf("provider requests before capability rejection = %s", got) } } func TestApolloProviderCapabilityRejectsHEVCLumaDowngrade(t *testing.T) { info := ManagementInfo{ ServerCodecModeSupport: 0x101, HasServerCodecModeSupport: true, MaxLumaPixelsHEVC: 1920 * 1080, HasMaxLumaPixelsHEVC: true, } policy := protocol.ProviderStreamPolicy{ ResolutionWidth: 3840, ResolutionHeight: 2160, Fps: 60, Codec: "HEVC", BitrateKbps: 80000, AudioEnabled: true, } if err := validateApolloProviderStreamPolicy(info, policy); !errors.Is(err, ErrProviderMalformed) { t.Fatalf("provider HEVC luma downgrade error = %v", err) } } func TestNativeApolloSetupRequiresModernEncryptedRTSPOrder(t *testing.T) { serverTLS, clientTLS := testTLS(t) streamListener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer streamListener.Close() controlServer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer controlServer.Close() audioServer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer audioServer.Close() videoServer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer videoServer.Close() streamHost, streamPortText, err := net.SplitHostPort(streamListener.Addr().String()) if err != nil { t.Fatal(err) } streamPort, err := strconv.ParseInt(streamPortText, 10, 64) if err != nil { t.Fatal(err) } type fixtureKeyMaterial struct { key []byte keyID uint32 } keyReady := make(chan []byte, 1) keyMaterialReady := make(chan fixtureKeyMaterial, 1) clipboardWrites := make(chan string, 1) cancelCalls := make(chan struct{}, 1) streamDone := make(chan error, 1) go func() { key := <-keyReady codec, codecErr := newEncryptedRTSPCodec(key) if codecErr != nil { streamDone <- codecErr return } expectedMethods := []string{"OPTIONS", "DESCRIBE", "SETUP", "SETUP", "SETUP", "ANNOUNCE", "PLAY"} expectedTargets := []string{"rtspenc://" + streamListener.Addr().String(), "rtspenc://" + streamListener.Addr().String(), "streamid=audio/0/0", "streamid=video/0/0", "streamid=control/13/0", "streamid=control/13/0", "/"} describeBody := "a=x-ss-general.featureFlags:1\r\na=x-ss-general.encryptionSupported:7\r\na=x-ss-general.encryptionRequested:1\r\na=fmtp:97 surround-params=21101\r\n" responses := []string{ "RTSP/1.0 200 OK\r\nCSeq: 1\r\n\r\n", fmt.Sprintf("RTSP/1.0 200 OK\r\nCSeq: 2\r\nContent-Type: application/sdp\r\nContent-Length: %d\r\n\r\n%s", len(describeBody), describeBody), fmt.Sprintf("RTSP/1.0 200 OK\r\nCSeq: 3\r\nSession: fixture-session;timeout=90\r\nTransport: unicast;server_port=%d\r\nX-SS-Ping-Payload: 0123456789abcdef\r\n\r\n", audioServer.LocalAddr().(*net.UDPAddr).Port), fmt.Sprintf("RTSP/1.0 200 OK\r\nCSeq: 4\r\nSession: fixture-session\r\nTransport: unicast;server_port=%d\r\nX-SS-Ping-Payload: fedcba9876543210\r\n\r\n", videoServer.LocalAddr().(*net.UDPAddr).Port), fmt.Sprintf("RTSP/1.0 200 OK\r\nCSeq: 5\r\nSession: fixture-session\r\nTransport: unicast;server_port=%d\r\nX-SS-Connect-Data: 305419896\r\n\r\n", controlServer.LocalAddr().(*net.UDPAddr).Port), "RTSP/1.0 200 OK\r\nCSeq: 6\r\nSession: fixture-session\r\n\r\n", "RTSP/1.0 200 OK\r\nCSeq: 7\r\nSession: fixture-session\r\n\r\n", } for index, method := range expectedMethods { connection, acceptErr := streamListener.Accept() if acceptErr != nil { streamDone <- acceptErr return } header := make([]byte, encryptedRTSPHeaderSize) if _, readErr := io.ReadFull(connection, header); readErr != nil { _ = connection.Close() streamDone <- readErr return } length := binary.BigEndian.Uint32(header[:4]) & 0x7fffffff frame := make([]byte, encryptedRTSPHeaderSize+int(length)) copy(frame, header) if _, readErr := io.ReadFull(connection, frame[encryptedRTSPHeaderSize:]); readErr != nil { _ = connection.Close() streamDone <- readErr return } plaintext, decryptErr := codec.OpenClient(frame) firstLine, _, _ := strings.Cut(string(plaintext), "\r\n") if decryptErr != nil || firstLine != method+" "+expectedTargets[index]+" RTSP/1.0" || !strings.Contains(string(plaintext), "CSeq: "+strconv.Itoa(index+1)+"\r\n") { _, _ = connection.Write(hostEncryptedRTSPFrame(t, key, uint32(index+1), []byte("RTSP/1.0 400 Bad Request\r\nCSeq: "+strconv.Itoa(index+1)+"\r\n\r\n"))) _ = connection.Close() streamDone <- ErrProviderMalformed return } if method == "ANNOUNCE" { for _, required := range []string{ "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") { _ = connection.Close() streamDone <- ErrProviderMalformed return } } } _, writeErr := connection.Write(hostEncryptedRTSPFrame(t, key, uint32(index+1), []byte(responses[index]))) _ = connection.Close() if writeErr != nil { streamDone <- writeErr return } } streamDone <- nil }() management := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.TLS == nil || len(request.TLS.PeerCertificates) != 1 { http.Error(response, "mTLS required", http.StatusUnauthorized) return } switch request.URL.Path { case "/serverinfo": _, _ = response.Write([]byte("apollo-server2571869449984")) case "/applist": if request.URL.Query().Get("uniqueid") != "paired-client" { http.Error(response, "wrong client", http.StatusBadRequest) return } _, _ = response.Write([]byte("42")) case "/launch": query := request.URL.Query() if query.Get("uniqueid") != "paired-client" || query.Get("appid") != "42" || query.Get("corever") != "1" || query.Get("localAudioPlayMode") != "0" { http.Error(response, "wrong launch", http.StatusBadRequest) return } key, decodeErr := hex.DecodeString(query.Get("rikey")) if decodeErr != nil || len(key) != 16 || query.Get("rikeyid") == "" { http.Error(response, "bad key", http.StatusBadRequest) return } keyID, keyIDErr := strconv.ParseUint(query.Get("rikeyid"), 10, 32) if keyIDErr != nil { http.Error(response, "bad key id", http.StatusBadRequest) return } keyReady <- key keyMaterialReady <- fixtureKeyMaterial{key: append([]byte(nil), key...), keyID: uint32(keyID)} _, _ = response.Write([]byte("rtspenc://" + streamListener.Addr().String() + "")) case "/resume": _, _ = response.Write([]byte("")) case "/cancel": if request.Method != http.MethodGet { http.Error(response, "cancel must be GET", http.StatusMethodNotAllowed) return } cancelCalls <- struct{}{} _, _ = response.Write([]byte("1")) case "/actions/clipboard": if request.URL.Query().Get("type") != "text" || (request.Method != http.MethodGet && request.Method != http.MethodPost) { http.Error(response, "bad clipboard request", http.StatusBadRequest) return } if request.Method == http.MethodPost { body, readErr := io.ReadAll(request.Body) if readErr != nil || string(body) != "client clipboard" { http.Error(response, "bad clipboard body", http.StatusBadRequest) return } clipboardWrites <- string(body) return } _, _ = response.Write([]byte("fixture clipboard")) default: http.NotFound(response, request) } })) management.TLS = serverTLS management.StartTLS() defer management.Close() managementHost, managementPortText, err := net.SplitHostPort(management.Listener.Addr().String()) if err != nil { t.Fatal(err) } managementPort, err := strconv.ParseInt(managementPortText, 10, 64) if err != nil { t.Fatal(err) } pinned := sha256.Sum256(serverTLS.Certificates[0].Certificate[0]) work := protocol.ProviderSessionWork{ 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]}}), ClipboardPolicy: protocol.ClipboardPolicy{MaxTextBytes: 65536, MaxUpdatesPerMinute: 30}, ProviderApplicationTerminationAllowed: true, } backend := NewNativeApolloBackend() response, err := backend.Setup(context.Background(), LaunchRequest{SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work}, []byte("apollo-server2571869449984")) if err != nil { t.Fatalf("Setup() error = %v", err) } if parsed, err := ParseRTSPResponse(response); err != nil || parsed.Session != "fixture-session" { t.Fatalf("ParseRTSPResponse() = %+v, %v", parsed, err) } if err := <-streamDone; err != nil { t.Fatalf("stream transaction error = %v", err) } material := <-keyMaterialReady videoData, videoParity := sourceShapedEncryptedVideoFEC(t, material.key) audioPackets := make([][]byte, 0, apolloAudioDataShards) for index, payload := range [][]byte{{'A'}, {'B'}, {'C'}, {'D'}} { audioPackets = append(audioPackets, sourceShapedEncryptedAudioPacket(t, material.key, material.keyID, uint16(index), payload)) } mediaDone := make(chan error, 2) serveMedia := func(server *net.UDPConn, ping string, packets [][]byte) { buffer := make([]byte, apolloMediaMaximumPacket) count, remote, readErr := server.ReadFromUDP(buffer) if readErr != nil || count != 20 || string(buffer[:16]) != ping { if readErr != nil { mediaDone <- readErr } else { mediaDone <- ErrProviderMalformed } return } for _, packet := range packets { if _, writeErr := server.WriteToUDP(packet, remote); writeErr != nil { mediaDone <- writeErr return } } mediaDone <- nil } go serveMedia(audioServer, "0123456789abcdef", audioPackets) go serveMedia(videoServer, "fedcba9876543210", [][]byte{videoData, videoParity}) type observedControl struct { typeID uint16 reliable bool } controls := make(chan observedControl, 16) controlRemote := make(chan *net.UDPAddr, 1) controlDone := make(chan error, 1) go func() { buffer := make([]byte, apolloENetMaximumPacket) count, remote, readErr := controlServer.ReadFromUDP(buffer) if readErr != nil { controlDone <- readErr return } connect := buffer[:count] if count != 52 || connect[4] != apolloENetConnect|apolloENetAcknowledged || connect[5] != 0xff || binary.BigEndian.Uint32(connect[20:24]) != apolloENetChannels || binary.BigEndian.Uint32(connect[48:52]) != 305419896 { controlDone <- ErrProviderMalformed return } verify := make([]byte, 48) apolloENetHeader(verify, 0, 0, time.Now()) verify[4] = apolloENetVerifyConnect | apolloENetAcknowledged verify[5] = 0xff binary.BigEndian.PutUint16(verify[6:8], 1) binary.BigEndian.PutUint16(verify[8:10], 7) verify[10], verify[11] = 2, 3 binary.BigEndian.PutUint32(verify[12:16], 1400) binary.BigEndian.PutUint32(verify[16:20], 32768) binary.BigEndian.PutUint32(verify[20:24], apolloENetChannels) binary.BigEndian.PutUint32(verify[44:48], binary.BigEndian.Uint32(connect[44:48])) if _, writeErr := controlServer.WriteToUDP(verify, remote); writeErr != nil { controlDone <- writeErr return } controlRemote <- remote for { count, _, readErr = controlServer.ReadFromUDP(buffer) if readErr != nil { controlDone <- readErr return } packet := buffer[:count] if len(packet) < 8 { controlDone <- ErrProviderMalformed return } command, channel := packet[4]&apolloENetCommandMask, packet[5] sequence := binary.BigEndian.Uint16(packet[6:8]) switch command { case 1: continue case apolloENetSendReliable: if len(packet) < 10 || int(binary.BigEndian.Uint16(packet[8:10])) != len(packet)-10 { controlDone <- ErrProviderMalformed return } typeID, _ := sourceOpenClientControl(t, material.key, packet[10:]) controls <- observedControl{typeID: typeID, reliable: true} if _, writeErr := controlServer.WriteToUDP(sourceShapedENetAcknowledgePacket(7, 2, channel, sequence), remote); writeErr != nil { controlDone <- writeErr return } case apolloENetSendUnsequenced: if len(packet) < 12 || int(binary.BigEndian.Uint16(packet[10:12])) != len(packet)-12 { controlDone <- ErrProviderMalformed return } typeID, _ := sourceOpenClientControl(t, material.key, packet[12:]) controls <- observedControl{typeID: typeID} case apolloENetPing: if _, writeErr := controlServer.WriteToUDP(sourceShapedENetAcknowledgePacket(7, 2, channel, sequence), remote); writeErr != nil { controlDone <- writeErr return } case apolloENetDisconnect: _, writeErr := controlServer.WriteToUDP(sourceShapedENetAcknowledgePacket(7, 2, channel, sequence), remote) controlDone <- writeErr return default: controlDone <- ErrProviderMalformed return } } }() request := LaunchRequest{SessionID: "session-1", ProviderProfile: ProviderProfileApollo, ProviderWork: work} parsed, err := ParseRTSPResponse(response) if err != nil { t.Fatal(err) } session, err := backend.Open(context.Background(), request, parsed) if err != nil { t.Fatalf("Open() error = %v", err) } if err := session.Ready(context.Background()); err != nil { t.Fatalf("Ready() error = %v", err) } if clipboard, clipboardErr := session.ReadClipboard(context.Background()); clipboardErr != nil || clipboard != "fixture clipboard" { t.Fatalf("ReadClipboard() = %q, %v", clipboard, clipboardErr) } if err := session.WriteClipboard(context.Background(), "client clipboard"); err != nil { t.Fatalf("WriteClipboard() error = %v", err) } select { case <-clipboardWrites: case <-time.After(time.Second): t.Fatal("provider did not receive authenticated clipboard write") } awaitControl := func(want uint16, reliable bool) { deadline := time.NewTimer(time.Second) defer deadline.Stop() for { select { case observed := <-controls: if observed.typeID == want && observed.reliable == reliable { return } case <-deadline.C: t.Fatalf("provider did not receive control %#x", want) } } } awaitControl(apolloControlTypeIDR, true) awaitControl(apolloControlTypeStart, true) if err := session.Input(context.Background(), InputEvent{Device: "keyboard", Code: 7, Pressed: true}); err != nil { t.Fatal(err) } awaitControl(apolloControlTypeInput, true) if err := session.Feedback(context.Background(), Feedback{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}}); err != nil { t.Fatal(err) } awaitControl(apolloControlTypeFEC, false) select { case media := <-session.Video(): payload := media.Payload if len(payload) != 1001 || payload[0] != 'A' || payload[1000] != 'B' { t.Fatalf("source-shaped video relay = %x", payload) } case <-time.After(time.Second): t.Fatal("source-shaped video was not relayed") } select { case media := <-session.Audio(): payload := media.Payload if string(payload) != "A" { t.Fatalf("source-shaped audio relay = %x", payload) } case <-time.After(time.Second): t.Fatal("source-shaped audio was not relayed") } remote := <-controlRemote hostTermination := sourceSealHostControl(t, material.key, 0, apolloControlTypeTerm, []byte{1, 2, 3, 4}) if _, err := controlServer.WriteToUDP(sourceShapedENetReliablePacketOn(7, 2, apolloChannelGeneric, 1, hostTermination), remote); err != nil { t.Fatal(err) } select { case event := <-session.Events(): if event.Kind != ProviderEventTerminated || string(event.Payload) != string([]byte{1, 2, 3, 4}) { t.Fatalf("provider termination event = %#v", event) } case <-time.After(time.Second): t.Fatal("encrypted host termination was not forwarded") } terminateCtx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := session.Terminate(terminateCtx); err != nil { t.Fatalf("Terminate() error = %v", err) } select { case <-cancelCalls: case <-time.After(time.Second): t.Fatal("authorized provider application cancellation was not sent") } if err := <-controlDone; err != nil { t.Fatalf("control fake = %v", err) } for range 2 { if err := <-mediaDone; err != nil { t.Fatalf("media fake = %v", err) } } } func TestValidateApolloDescribeRejectsUnknownLines(t *testing.T) { message := apolloRTSPMessage{ headers: map[string]string{"content-type": "application/sdp"}, body: []byte("a=x-ss-general.featureFlags:1\r\n" + "a=x-ss-general.encryptionSupported:7\r\n" + "a=x-ss-general.encryptionRequested:1\r\n" + "a=fmtp:97 surround-params=21101\r\n" + "unexpected-source-line\r\n"), } if err := validateApolloDescribe(message); err == nil { t.Fatal("validateApolloDescribe() accepted an unknown source line") } } func TestNativeApolloTerminateSkipsProviderCancelWithoutServerPolicy(t *testing.T) { session := newNativeApolloSession("session-1") close(session.readDone) session.managementClient = &http.Client{Transport: nativeRoundTripFunc(func(request *http.Request) (*http.Response, error) { t.Fatalf("unauthorized provider cancellation request: %s %s", request.Method, request.URL) return nil, fmt.Errorf("unexpected provider cancellation") })} session.managementHost = "provider.invalid" session.managementPort = 47984 if err := session.Terminate(context.Background()); err != nil { t.Fatalf("Terminate() error = %v", err) } } func TestNativeApolloTerminateRetainsCleanupPendingAfterFailure(t *testing.T) { session := newNativeApolloSession("session-1") failed, cancel := context.WithCancel(context.Background()) cancel() if err := session.Terminate(failed); !errors.Is(err, context.Canceled) { t.Fatalf("first Terminate() error = %v", err) } if state := session.State(); state.State != ProviderStateCleanup || !state.CleanupPending { t.Fatalf("failed cleanup state = %#v", state) } if err := session.Terminate(context.Background()); !errors.Is(err, context.Canceled) { t.Fatalf("second Terminate() error = %v", err) } if state := session.State(); state.State != ProviderStateCleanup || !state.CleanupPending { t.Fatalf("repeated cleanup state = %#v", state) } } func TestApolloReconnectUsesResumeWithFreshRIK(t *testing.T) { work := protocol.ProviderSessionWork{ReconnectSequence: 1, ApplicationID: "42", ClientID: "paired-client"} path, values := apolloSessionRequest(work, []byte("0123456789abcdef"), 0x01020304) if path != "/resume" || values.Get("rikey") != "30313233343536373839616263646566" || values.Get("rikeyid") != "16909060" || values.Get("localAudioPlayMode") != "0" { t.Fatalf("resume request = path %q values %#v", path, values) } if values.Get("appid") != "" || values.Get("uniqueid") != "" || values.Get("corever") != "" { t.Fatalf("resume request leaked launch-only values %#v", values) } } func TestNativeApolloSessionRelaysOnlyAuthenticatedEncodedUDPMedia(t *testing.T) { key := []byte("0123456789abcdef") audioServer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer audioServer.Close() videoServer, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer videoServer.Close() audioClient, err := net.DialUDP("udp", nil, audioServer.LocalAddr().(*net.UDPAddr)) if err != nil { t.Fatal(err) } videoClient, err := net.DialUDP("udp", nil, videoServer.LocalAddr().(*net.UDPAddr)) if err != nil { t.Fatal(err) } session := newNativeApolloSession("session-media") const keyID = 0x01020304 session.media, err = newApolloMediaCodec(key, keyID) if err != nil { t.Fatal(err) } session.audioConn, session.videoConn = audioClient, videoClient go session.readUDPMedia() videoPacket := sourceShapedEncryptedVideoPacket(t, key, []byte{0x01, 0x02, 0x03}) if _, err := videoServer.WriteToUDP(videoPacket, videoClient.LocalAddr().(*net.UDPAddr)); err != nil { t.Fatal(err) } wantAudio := [][]byte{{0xf8, 0x08}, {0xf8, 0x09}, {0xf8, 0x0a}, {0xf8, 0x0b}} for index, payload := range wantAudio { packet := sourceShapedEncryptedAudioPacket(t, key, keyID, uint16(8+index), payload) if _, err := audioServer.WriteToUDP(packet, audioClient.LocalAddr().(*net.UDPAddr)); err != nil { t.Fatal(err) } } select { case media := <-session.Video(): payload := media.Payload if string(payload) != string([]byte{0x01, 0x02, 0x03}) { t.Fatalf("video relay = %x, want encoded payload", payload) } case <-time.After(time.Second): t.Fatal("encrypted video was not relayed") } for _, want := range wantAudio { select { case media := <-session.Audio(): payload := media.Payload if string(payload) != string(want) { t.Fatalf("audio relay = %x, want %x", payload, want) } case <-time.After(time.Second): t.Fatal("encrypted audio was not relayed") } } dataPacket, parityPacket := sourceShapedEncryptedVideoFEC(t, key) for _, packet := range [][]byte{dataPacket, parityPacket} { if _, err := videoServer.WriteToUDP(packet, videoClient.LocalAddr().(*net.UDPAddr)); err != nil { t.Fatal(err) } } select { case media := <-session.Video(): payload := media.Payload if len(payload) != 1001 || payload[0] != 'A' || payload[999] != 'A' || payload[1000] != 'B' { t.Fatalf("FEC video relay = %x", payload) } case <-time.After(time.Second): t.Fatal("encrypted FEC video was not recovered") } for _, packet := range sourceShapedEncryptedAudioFEC(t, key, keyID) { if _, err := audioServer.WriteToUDP(packet, audioClient.LocalAddr().(*net.UDPAddr)); err != nil { t.Fatal(err) } } for _, want := range [][]byte{{'A'}, {'B'}, {'C'}, {'D'}} { select { case media := <-session.Audio(): payload := media.Payload if string(payload) != string(want) { t.Fatalf("FEC audio relay = %x, want %x", payload, want) } case <-time.After(time.Second): 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 media := <-session.Audio(): payload := media.Payload 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 { t.Fatalf("Terminate() error = %v", err) } } func TestNativeApolloTerminateReleasesPressedProviderInput(t *testing.T) { server, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")}) if err != nil { t.Fatal(err) } defer server.Close() client, err := net.DialUDP("udp", nil, server.LocalAddr().(*net.UDPAddr)) if err != nil { t.Fatal(err) } peer, err := newApolloENetPeer(client, time.Now) if err != nil { t.Fatal(err) } peer.state, peer.peerID, peer.outboundSession = apolloENetConnected, 1, 1 codecKey := []byte("0123456789abcdef") codec, err := newApolloControlCodec(codecKey) if err != nil { t.Fatal(err) } session := newNativeApolloSession("session-terminate") session.enet, session.control = peer, codec close(session.readDone) packets := make(chan []byte, 3) go func() { defer close(packets) buffer := make([]byte, apolloENetMaximumPacket) for { _ = server.SetReadDeadline(time.Now().Add(time.Second)) count, _, readErr := server.ReadFromUDP(buffer) if readErr != nil { return } packet := append([]byte(nil), buffer[:count]...) packets <- packet if len(packet) >= 5 && packet[4]&apolloENetCommandMask == apolloENetDisconnect { peer.close(nil) return } } }() if err := session.Input(context.Background(), InputEvent{Device: "keyboard", Code: 7, Pressed: true}); err != nil { t.Fatal(err) } terminateCtx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := session.Terminate(terminateCtx); err != nil { t.Fatalf("Terminate() error = %v", err) } var observed [][]byte for packet := range packets { observed = append(observed, packet) } if len(observed) != 3 { t.Fatalf("provider packets = %d, want pressed input, release, disconnect", len(observed)) } typeID, payload := sourceOpenClientControl(t, codecKey, observed[1][10:]) if typeID != apolloControlTypeInput || len(payload) < 8 || binary.LittleEndian.Uint32(payload[4:8]) != 4 { t.Fatalf("release packet = type %#x payload %x", typeID, payload) } } func TestNativeApolloSessionForwardsEncryptedHostFeedback(t *testing.T) { key := []byte("0123456789abcdef") codec, err := newApolloControlCodec(key) if err != nil { t.Fatal(err) } session := newNativeApolloSession("session-feedback") session.control = codec messages := []struct { typeID uint16 payload []byte want ProviderEvent }{ {apolloControlTypeRumble, []byte{0, 0, 0, 0, 1, 0, 0x34, 0x12, 0x78, 0x56}, ProviderEvent{Kind: ProviderEventRumble, Payload: []byte{1, 0x12, 0x34, 0x56, 0x78}}}, {apolloControlTypeHDR, append([]byte{1}, make([]byte, 26)...), ProviderEvent{Kind: ProviderEventHDR, Payload: []byte{1}}}, {apolloControlTypeTerm, []byte{1, 2, 3, 4}, ProviderEvent{Kind: ProviderEventTerminated, Payload: []byte{1, 2, 3, 4}}}, } for sequence, message := range messages { session.handleApolloControlPayload(apolloChannelGeneric, true, sourceSealHostControl(t, key, uint32(sequence), message.typeID, message.payload)) select { case event := <-session.Events(): if event.Kind != message.want.Kind || string(event.Payload) != string(message.want.Payload) { t.Fatalf("provider event = %#v, want %#v", event, message.want) } case <-time.After(time.Second): t.Fatalf("host event %#x was not forwarded", message.typeID) } } } func TestApolloTerminalEventSurvivesFullFeedbackQueue(t *testing.T) { session := newNativeApolloSession("session-full-feedback") key := []byte("0123456789abcdef") codec, err := newApolloControlCodec(key) if err != nil { t.Fatal(err) } session.control = codec for range cap(session.events) { session.events <- ProviderEvent{Kind: ProviderEventRumble, Payload: []byte{1, 0, 0, 0, 0}} } session.handleApolloControlPayload( apolloChannelGeneric, true, sourceSealHostControl(t, key, 0, apolloControlTypeTerm, []byte{1, 2, 3, 4}), ) foundTerminal := false for range cap(session.events) { event := <-session.events foundTerminal = foundTerminal || event.Kind == ProviderEventTerminated } if !foundTerminal { t.Fatal("encrypted host termination was lost behind a full feedback queue") } } func TestPushLatestDropsExactlyOneOldPayload(t *testing.T) { queue := make(chan []byte, 1) if dropped := pushLatest(queue, []byte("old")); dropped { t.Fatal("first media payload dropped") } if dropped := pushLatest(queue, []byte("new")); !dropped { t.Fatal("bounded media queue did not report a drop") } if got := string(<-queue); got != "new" { t.Fatalf("bounded media queue payload = %q", got) } } func TestNativeProviderVideoQueueBoundsRealFrames(t *testing.T) { const maximumQueuedVideoBytes = 4 << 20 session := newNativeApolloSession("bounded-video") frame := bytes.Repeat([]byte{0x65}, 768<<10) for index := 0; index < 12; index++ { if !session.enqueueMedia(session.video, append([]byte(nil), frame...), time.Now()) { t.Fatalf("frame %d was not accepted", index) } } var queuedBytes int for { select { case media := <-session.Video(): queuedBytes += len(media.Payload) default: if queuedBytes > maximumQueuedVideoBytes { t.Fatalf("video queue retained %d bytes, limit %d", queuedBytes, maximumQueuedVideoBytes) } if drops := session.Telemetry().MediaDrops; drops != 7 { t.Fatalf("latest-frame replacements = %d, want 7", drops) } if maximum := session.mediaQueueMaximum.Load(); maximum > nativeApolloVideoQueuePackets { t.Fatalf("maximum video queue entries = %d", maximum) } if maximum := session.mediaQueueMaximumBytes.Load(); maximum > maximumQueuedVideoBytes { t.Fatalf("maximum video queue bytes = %d", maximum) } return } } } func TestNativeProviderVideoQueueExpiresResidence(t *testing.T) { session := newNativeApolloSession("expiring-video") if !session.enqueueMedia(session.video, []byte("stale-frame"), time.Now()) { t.Fatal("video frame was not accepted") } time.Sleep(nativeApolloVideoQueueLatency + 25*time.Millisecond) select { case media := <-session.Video(): t.Fatalf("expired video remained queued: %#v", media) default: } if drops := session.Telemetry().MediaDrops; drops != 1 { t.Fatalf("expired video drops = %d, want 1", drops) } } func sourceShapedEncryptedVideoPacket(t *testing.T, key, encoded []byte) []byte { t.Helper() payload := make([]byte, apolloVideoShardPayloadSize) payload[0], payload[3] = 0x01, 0x01 binary.LittleEndian.PutUint16(payload[4:6], uint16(8+len(encoded))) copy(payload[8:], encoded) plaintext := sourceShapedVideoRaw(42, 7, 1, 0x07, 1, 0, 0, payload) return sourceEncryptVideoRaw(t, key, plaintext, "0123456789aV") } func sourceShapedEncryptedVideoFEC(t *testing.T, key []byte) ([]byte, []byte) { t.Helper() firstPayload := make([]byte, apolloVideoShardPayloadSize) firstPayload[0], firstPayload[3] = 0x01, 0x01 binary.LittleEndian.PutUint16(firstPayload[4:6], 1) for index := 8; index < len(firstPayload); index++ { firstPayload[index] = 'A' } secondPayload := make([]byte, apolloVideoShardPayloadSize) secondPayload[0] = 'B' first := sourceShapedVideoRaw(43, 100, 100, 0x05, 2, 50, 0, firstPayload) second := sourceShapedVideoRaw(43, 101, 101, 0x03, 2, 50, 1, secondPayload) parity := make([]byte, len(first)) for index := range parity { parity[index] = first[index] ^ sourceGFMultiply(second[index], 142) } sourceConfigureVideoShard(first, 43, 100, 0, 2, 50, 0) sourceConfigureVideoShard(second, 43, 101, 1, 2, 50, 1) sourceConfigureVideoShard(parity, 43, 102, 2, 2, 50, 2) return sourceEncryptVideoRaw(t, key, second, "0123456789bV"), sourceEncryptVideoRaw(t, key, parity, "0123456789cV") } func sourceShapedVideoRaw(frame uint32, sequence uint16, streamIndex uint32, flags byte, dataShards, percentage, shardIndex int, payload []byte) []byte { raw := make([]byte, apolloVideoRawPacketSize) binary.LittleEndian.PutUint32(raw[16:20], streamIndex<<8) binary.LittleEndian.PutUint32(raw[20:24], frame) raw[24], raw[26] = flags, 0x10 binary.LittleEndian.PutUint32(raw[28:32], uint32(shardIndex<<12|dataShards<<22|percentage<<4)) copy(raw[32:], payload) sourceConfigureVideoShard(raw, frame, sequence, streamIndex, dataShards, percentage, shardIndex) return raw } func sourceConfigureVideoShard(raw []byte, frame uint32, sequence uint16, streamIndex uint32, dataShards, percentage, shardIndex int) { raw[0] = 0x90 binary.BigEndian.PutUint16(raw[2:4], sequence) binary.BigEndian.PutUint32(raw[4:8], 99) binary.LittleEndian.PutUint32(raw[20:24], frame) raw[27] = 0 binary.LittleEndian.PutUint32(raw[28:32], uint32(shardIndex<<12|dataShards<<22|percentage<<4)) } func sourceEncryptVideoRaw(t *testing.T, key, plaintext []byte, iv string) []byte { t.Helper() if len(plaintext) != apolloVideoRawPacketSize || len(iv) != 12 { t.Fatal("invalid source video fixture") } block, err := aes.NewCipher(key) if err != nil { t.Fatal(err) } aead, err := cipher.NewGCM(block) if err != nil { t.Fatal(err) } sealed := aead.Seal(nil, []byte(iv), plaintext, nil) packet := make([]byte, 32+len(plaintext)) copy(packet[:12], iv) binary.LittleEndian.PutUint32(packet[12:16], binary.LittleEndian.Uint32(plaintext[20:24])) copy(packet[16:32], sealed[len(plaintext):]) copy(packet[32:], sealed[:len(plaintext)]) return packet } func sourceOpenClientControl(t *testing.T, key, packet []byte) (uint16, []byte) { t.Helper() if len(packet) < apolloControlHeaderSize+apolloControlTagSize+apolloControlInnerSize || binary.LittleEndian.Uint16(packet[:2]) != apolloControlOuterType || int(binary.LittleEndian.Uint16(packet[2:4])) != len(packet)-4 { t.Fatalf("control packet = %x", packet) } block, err := aes.NewCipher(key) if err != nil { t.Fatal(err) } aead, err := cipher.NewGCM(block) if err != nil { t.Fatal(err) } nonce := make([]byte, 12) binary.LittleEndian.PutUint32(nonce, binary.LittleEndian.Uint32(packet[4:8])) nonce[10], nonce[11] = 'C', 'C' sealed := append([]byte(nil), packet[24:]...) sealed = append(sealed, packet[8:24]...) plaintext, err := aead.Open(nil, nonce, sealed, nil) if err != nil || len(plaintext) < apolloControlInnerSize || int(binary.LittleEndian.Uint16(plaintext[2:4])) != len(plaintext)-apolloControlInnerSize { t.Fatalf("control open = %x, %v", packet, err) } return binary.LittleEndian.Uint16(plaintext[:2]), append([]byte(nil), plaintext[4:]...) } func sourceSealHostControl(t *testing.T, key []byte, sequence uint32, typeID uint16, payload []byte) []byte { t.Helper() block, err := aes.NewCipher(key) if err != nil { t.Fatal(err) } aead, err := cipher.NewGCM(block) if err != nil { t.Fatal(err) } inner := make([]byte, apolloControlInnerSize+len(payload)) binary.LittleEndian.PutUint16(inner[:2], typeID) binary.LittleEndian.PutUint16(inner[2:4], uint16(len(payload))) copy(inner[4:], payload) nonce := make([]byte, 12) binary.LittleEndian.PutUint32(nonce, sequence) nonce[10], nonce[11] = 'H', 'C' sealed := aead.Seal(nil, nonce, inner, nil) packet := make([]byte, apolloControlHeaderSize+len(sealed)) binary.LittleEndian.PutUint16(packet[:2], apolloControlOuterType) binary.LittleEndian.PutUint16(packet[2:4], uint16(4+len(sealed))) binary.LittleEndian.PutUint32(packet[4:8], sequence) copy(packet[8:24], sealed[len(inner):]) copy(packet[24:], sealed[:len(inner)]) return packet } func sourceGFMultiply(first, second byte) byte { var product byte for second != 0 { if second&1 != 0 { product ^= first } high := first & 0x80 first <<= 1 if high != 0 { first ^= 0x1d } second >>= 1 } return product } func sourceShapedEncryptedAudioPacket(t *testing.T, key []byte, keyID uint32, sequence uint16, encoded []byte) []byte { return sourceShapedEncryptedAudioPacketWithHeaders(t, key, keyID, sequence, 99, 1, encoded) } func sourceShapedEncryptedAudioPacketWithHeaders(t *testing.T, key []byte, keyID uint32, sequence uint16, timestamp, ssrc uint32, encoded []byte) []byte { t.Helper() packet := make([]byte, 12) packet[0], packet[1] = 0x80, 97 binary.BigEndian.PutUint16(packet[2:4], sequence) binary.BigEndian.PutUint32(packet[4:8], timestamp) binary.BigEndian.PutUint32(packet[8:12], ssrc) padded := append([]byte(nil), encoded...) padding := aes.BlockSize - len(padded)%aes.BlockSize for range padding { padded = append(padded, byte(padding)) } iv := make([]byte, aes.BlockSize) binary.BigEndian.PutUint32(iv, keyID+uint32(sequence)) block, err := aes.NewCipher(key) if err != nil { t.Fatal(err) } cipher.NewCBCEncrypter(block, iv).CryptBlocks(padded, padded) return append(packet, padded...) } func sourceShapedEncryptedAudioFEC(t *testing.T, key []byte, keyID uint32) [][]byte { t.Helper() const base = uint16(12) const timestamp = uint32(100) data := make([][]byte, apolloAudioDataShards) for index, payload := range [][]byte{{'A'}, {'B'}, {'C'}, {'D'}} { data[index] = sourceShapedEncryptedAudioPacketWithHeaders(t, key, keyID, base+uint16(index), timestamp+uint32(index*5), 0, payload) } parity := make([]byte, len(data[0])-apolloRTPHeaderSize) for index, coefficient := range sourceAudioFECRow() { for offset, value := range data[index][apolloRTPHeaderSize:] { parity[offset] ^= sourceGFMultiply(value, coefficient) } } fec := make([]byte, apolloRTPHeaderSize+12+len(parity)) fec[0], fec[1] = 0x80, 127 binary.BigEndian.PutUint16(fec[2:4], base+apolloAudioDataShards) fec[apolloRTPHeaderSize+1] = 97 binary.BigEndian.PutUint16(fec[apolloRTPHeaderSize+2:apolloRTPHeaderSize+4], base) binary.BigEndian.PutUint32(fec[apolloRTPHeaderSize+4:apolloRTPHeaderSize+8], timestamp) binary.BigEndian.PutUint32(fec[apolloRTPHeaderSize+8:apolloRTPHeaderSize+12], 0) copy(fec[apolloRTPHeaderSize+12:], parity) return [][]byte{data[1], data[2], data[3], fec} } func sourceAudioFECRow() []byte { return []byte{0x77, 0x40, 0x38, 0x0e} } func certificatePEM(t *testing.T, certificate tls.Certificate) string { t.Helper() if len(certificate.Certificate) == 0 { t.Fatal("expected certificate") } return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Certificate[0]})) } func privateKeyPEM(t *testing.T, certificate tls.Certificate) string { t.Helper() encoded, err := x509.MarshalPKCS8PrivateKey(certificate.PrivateKey) if err != nil { t.Fatal(err) } return string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encoded})) }