github.com/livekit/protocol@v1.39.3/sdp/sdp_test.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sdp
    16  
    17  import (
    18  	"testing"
    19  
    20  	pionsdp "github.com/pion/sdp/v3"
    21  	"github.com/pion/webrtc/v4"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestSDPUtilFunctions(t *testing.T) {
    26  	sdp := "v=0\r\no=- 4648475892259889561 3 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=ice-ufrag:1hhfzwf0ijpzm\r\na=ice-pwd:jm5puo2ab1op3vs59ca53bdk7s\r\na=fingerprint:sha-256 40:42:FB:47:87:52:BF:CB:EC:3A:DF:EB:06:DA:2D:B7:2F:59:42:10:23:7B:9D:4C:C9:58:DD:FF:A2:8F:17:67\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=setup:passive\r\na=mid:0\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:96 H264/90000\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 goog-remb\r\na=fmtp:96 packetization-mode=1;profile-level-id=42e01f\r\na=ssrc:1505338584 cname:10000000b5810aac\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=setup:passive\r\na=mid:1\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=ssrc:697641945 cname:10000000b5810aac\r\n"
    27  	sd := webrtc.SessionDescription{
    28  		Type: webrtc.SDPTypeOffer,
    29  		SDP:  sdp,
    30  	}
    31  	parsed, err := sd.Unmarshal()
    32  	require.NoError(t, err)
    33  
    34  	require.Equal(t, "0", GetMidValue(parsed.MediaDescriptions[0]))
    35  	require.Equal(t, "1", GetMidValue(parsed.MediaDescriptions[1]))
    36  
    37  	fp, alg, err := ExtractFingerprint(parsed)
    38  	require.NoError(t, err)
    39  	require.Equal(t, "40:42:FB:47:87:52:BF:CB:EC:3A:DF:EB:06:DA:2D:B7:2F:59:42:10:23:7B:9D:4C:C9:58:DD:FF:A2:8F:17:67", fp)
    40  	require.Equal(t, "sha-256", alg)
    41  
    42  	dtlsRole := ExtractDTLSRole(parsed)
    43  	require.Equal(t, webrtc.DTLSRoleServer, dtlsRole)
    44  
    45  	ufrag, pwd, err := ExtractICECredential(parsed)
    46  	require.NoError(t, err)
    47  	require.Equal(t, "1hhfzwf0ijpzm", ufrag)
    48  	require.Equal(t, "jm5puo2ab1op3vs59ca53bdk7s", pwd)
    49  
    50  	streamID, found := ExtractStreamID(parsed.MediaDescriptions[0])
    51  	require.False(t, found)
    52  	require.Empty(t, streamID)
    53  
    54  	_, ok := GetSimulcastRids(parsed.MediaDescriptions[1])
    55  	require.False(t, ok)
    56  
    57  	codecs, err := CodecsFromMediaDescription(parsed.MediaDescriptions[0])
    58  	require.NoError(t, err)
    59  	expectedCodec := pionsdp.Codec{
    60  		PayloadType:  96,
    61  		Name:         "H264",
    62  		ClockRate:    90000,
    63  		Fmtp:         "packetization-mode=1;profile-level-id=42e01f",
    64  		RTCPFeedback: []string{"nack", "goog-remb"},
    65  	}
    66  	require.Equal(t, expectedCodec, codecs[0])
    67  
    68  	bundleMid, found := GetBundleMid(parsed)
    69  	require.True(t, found)
    70  	require.Equal(t, "0", bundleMid)
    71  }
    72  
    73  func TestSDPFragment(t *testing.T) {
    74  	fragment := "a=ice-lite\r\na=ice-options:trickle ice2\r\na=group:BUNDLE 0 1\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=mid:0\r\na=ice-ufrag:ysXw\r\na=ice-pwd:vw5LmwG4y/e6dPP/zAP9Gp5k\r\na=candidate:1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1\r\na=candidate:3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2\r\na=candidate:473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1\r\na=candidate:2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2\r\na=candidate:393455558 0 tcp 1518283007 [2401:4900:633c:959f:2037:680c:7c40:b3db] 9 typ host tcptype active\r\n"
    75  
    76  	lite := true
    77  	expectedSDPFragment := SDPFragment{
    78  		group: "BUNDLE 0 1",
    79  		ice: &sdpFragmentICE{
    80  			options: "trickle ice2",
    81  			lite:    &lite,
    82  		},
    83  		media: &sdpFragmentMedia{
    84  			info: "audio 9 UDP/TLS/RTP/SAVPF 111",
    85  			mid:  "0",
    86  			ice: &sdpFragmentICE{
    87  				ufrag: "ysXw",
    88  				pwd:   "vw5LmwG4y/e6dPP/zAP9Gp5k",
    89  			},
    90  			candidates: []string{
    91  				"1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1",
    92  				"3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2",
    93  				"473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1",
    94  				"2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2",
    95  				"393455558 0 tcp 1518283007 [2401:4900:633c:959f:2037:680c:7c40:b3db] 9 typ host tcptype active",
    96  			},
    97  		},
    98  	}
    99  
   100  	sdpFragment := &SDPFragment{}
   101  	err := sdpFragment.Unmarshal(fragment)
   102  	require.NoError(t, err)
   103  	require.Equal(t, expectedSDPFragment, *sdpFragment)
   104  
   105  	expectedMarshalled := "a=group:BUNDLE 0 1\r\na=ice-lite\r\na=ice-options:trickle ice2\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=mid:0\r\na=ice-ufrag:ysXw\r\na=ice-pwd:vw5LmwG4y/e6dPP/zAP9Gp5k\r\na=candidate:1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1\r\na=candidate:3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2\r\na=candidate:473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1\r\na=candidate:2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2\r\na=candidate:393455558 0 tcp 1518283007 [2401:4900:633c:959f:2037:680c:7c40:b3db] 9 typ host tcptype active\r\n"
   106  	marshalled, err := sdpFragment.Marshal()
   107  	require.NoError(t, err)
   108  	require.Equal(t, expectedMarshalled, marshalled)
   109  
   110  	expectedCandidates := []string{
   111  		"1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1",
   112  		"3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2",
   113  		"473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1",
   114  		"2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2",
   115  		"393455558 0 tcp 1518283007 [2401:4900:633c:959f:2037:680c:7c40:b3db] 9 typ host tcptype active",
   116  	}
   117  
   118  	sdpFragment1 := &SDPFragment{}
   119  	err = sdpFragment1.Unmarshal(marshalled)
   120  	require.NoError(t, err)
   121  	require.Equal(t, expectedSDPFragment, *sdpFragment1)
   122  	require.Equal(t, "0", sdpFragment1.Mid())
   123  	require.Equal(t, expectedCandidates, sdpFragment1.Candidates())
   124  	ufrag, pwd, err := sdpFragment1.ExtractICECredential()
   125  	require.NoError(t, err)
   126  	require.Equal(t, "ysXw", ufrag)
   127  	require.Equal(t, "vw5LmwG4y/e6dPP/zAP9Gp5k", pwd)
   128  
   129  	mismatchedMidFragment := "a=ice-options:trickle ice2\r\na=group:BUNDLE 0 1\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=mid:1\r\na=ice-ufrag:ysXw\r\na=ice-pwd:vw5LmwG4y/e6dPP/zAP9Gp5k\r\na=candidate:1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1\r\na=candidate:3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2\r\na=candidate:473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1\r\na=candidate:2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2\r\n"
   130  	sdpFragment2 := &SDPFragment{}
   131  	err = sdpFragment2.Unmarshal(mismatchedMidFragment)
   132  	require.Error(t, err)
   133  
   134  	sdp := "v=0\r\no=- 4648475892259889561 3 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=ice-options:trickle ice2\r\na=fingerprint:sha-256 40:42:FB:47:87:52:BF:CB:EC:3A:DF:EB:06:DA:2D:B7:2F:59:42:10:23:7B:9D:4C:C9:58:DD:FF:A2:8F:17:67\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:1hhfzwf0ijpzm\r\na=ice-pwd:jm5puo2ab1op3vs59ca53bdk7s\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=setup:passive\r\na=mid:0\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:96 H264/90000\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 goog-remb\r\na=fmtp:96 packetization-mode=1;profile-level-id=42e01f\r\na=ssrc:1505338584 cname:10000000b5810aac\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=setup:passive\r\na=mid:1\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=ssrc:697641945 cname:10000000b5810aac\r\n"
   135  	sd := webrtc.SessionDescription{
   136  		Type: webrtc.SDPTypeOffer,
   137  		SDP:  sdp,
   138  	}
   139  	parsed, _ := sd.Unmarshal()
   140  	err = sdpFragment1.PatchICECredentialAndCandidatesIntoSDP(parsed)
   141  	require.NoError(t, err)
   142  
   143  	ufrag, pwd, err = ExtractICECredential(parsed)
   144  	require.NoError(t, err)
   145  	require.Equal(t, "ysXw", ufrag)
   146  	require.Equal(t, "vw5LmwG4y/e6dPP/zAP9Gp5k", pwd)
   147  
   148  	candidates := []string{}
   149  	for _, a := range parsed.MediaDescriptions[0].Attributes {
   150  		if a.IsICECandidate() {
   151  			candidates = append(candidates, a.Value)
   152  		}
   153  	}
   154  	require.Equal(t, expectedCandidates, candidates)
   155  
   156  	sdpFragment3, err := ExtractSDPFragment(parsed)
   157  	require.NoError(t, err)
   158  
   159  	expectedSDPFragment3 := SDPFragment{
   160  		group: "BUNDLE 0 1",
   161  		ice: &sdpFragmentICE{
   162  			options: "trickle ice2",
   163  		},
   164  		media: &sdpFragmentMedia{
   165  			info: "video 9 UDP/TLS/RTP/SAVPF 96",
   166  			mid:  "0",
   167  			ice: &sdpFragmentICE{
   168  				ufrag: "ysXw",
   169  				pwd:   "vw5LmwG4y/e6dPP/zAP9Gp5k",
   170  			},
   171  			candidates: []string{
   172  				"1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1",
   173  				"3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2",
   174  				"473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1",
   175  				"2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2",
   176  				"393455558 0 tcp 1518283007 [2401:4900:633c:959f:2037:680c:7c40:b3db] 9 typ host tcptype active",
   177  			},
   178  		},
   179  	}
   180  	require.Equal(t, expectedSDPFragment3, *sdpFragment3)
   181  
   182  	marshalledSDPFragment3, err := sdpFragment3.Marshal()
   183  	require.NoError(t, err)
   184  	expectedMarshalledSDPFragment3 := "a=group:BUNDLE 0 1\r\na=ice-options:trickle ice2\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=mid:0\r\na=ice-ufrag:ysXw\r\na=ice-pwd:vw5LmwG4y/e6dPP/zAP9Gp5k\r\na=candidate:1387637174 1 udp 2122260223 192.0.2.1 61764 typ host generation 0 ufrag EsAw network-id 1\r\na=candidate:3471623853 1 udp 2122194687 198.51.100.2 61765 typ host generation 0 ufrag EsAw network-id 2\r\na=candidate:473322822 1 tcp 1518280447 192.0.2.1 9 typ host tcptype active generation 0 ufrag EsAw network-id 1\r\na=candidate:2154773085 1 tcp 1518214911 198.51.100.2 9 typ host tcptype active generation 0 ufrag EsAw network-id 2\r\na=candidate:393455558 0 tcp 1518283007 [2401:4900:633c:959f:2037:680c:7c40:b3db] 9 typ host tcptype active\r\n"
   185  	require.Equal(t, expectedMarshalledSDPFragment3, marshalledSDPFragment3)
   186  }