test_peerConnection_extmapRenegotiation.html (14063B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <script type="application/javascript" src="pc.js"></script> 5 <script type="application/javascript" src="iceTestUtils.js"></script> 6 </head> 7 <body> 8 <pre id="test"> 9 <script type="application/javascript"> 10 createHTML({ 11 bug: "1799932", 12 title: "RTCPeerConnection check renegotiation of extmap" 13 }); 14 15 function setExtmap(sdp, uri, id) { 16 const regex = new RegExp(`a=extmap:[0-9]+(\/[a-z]+)? ${uri}`, 'g'); 17 if (id) { 18 return sdp.replaceAll(regex, `a=extmap:${id}$1 ${uri}`); 19 } else { 20 return sdp.replaceAll(regex, `a=unknownattr`); 21 } 22 } 23 24 function getExtmap(sdp, uri) { 25 const regex = new RegExp(`a=extmap:([0-9]+)(\/[a-z]+)? ${uri}`); 26 return sdp.match(regex)[1]; 27 } 28 29 function replaceExtUri(sdp, oldUri, newUri) { 30 const regex = new RegExp(`(a=extmap:[0-9]+\/[a-z]+)? ${oldUri}`, 'g'); 31 return sdp.replaceAll(regex, `$1 ${newUri}`); 32 } 33 34 const tests = [ 35 async function checkAudioMidChange() { 36 const pc1 = new RTCPeerConnection(); 37 const pc2 = new RTCPeerConnection(); 38 39 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 40 pc1.addTrack(stream.getTracks()[0]); 41 pc2.addTrack(stream.getTracks()[0]); 42 43 await connect(pc1, pc2, 32000, "Initial connection"); 44 45 // Sadly, there's no way to tell the offerer to change the extmap. Other 46 // types of endpoint could conceivably do this, so we at least don't want 47 // to crash. 48 // TODO: Would be nice to be able to test this with an endpoint that 49 // actually changes the ids it uses. 50 const reoffer = await pc1.createOffer(); 51 reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14); 52 info(`New reoffer: ${reoffer.sdp}`); 53 await pc2.setRemoteDescription(reoffer); 54 await pc2.setLocalDescription(); 55 await wait(2000); 56 }, 57 58 async function checkVideoMidChange() { 59 const pc1 = new RTCPeerConnection(); 60 const pc2 = new RTCPeerConnection(); 61 62 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 63 pc1.addTrack(stream.getTracks()[0]); 64 pc2.addTrack(stream.getTracks()[0]); 65 66 await connect(pc1, pc2, 32000, "Initial connection"); 67 68 // Sadly, there's no way to tell the offerer to change the extmap. Other 69 // types of endpoint could conceivably do this, so we at least don't want 70 // to crash. 71 // TODO: Would be nice to be able to test this with an endpoint that 72 // actually changes the ids it uses. 73 const reoffer = await pc1.createOffer(); 74 reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14); 75 info(`New reoffer: ${reoffer.sdp}`); 76 await pc2.setRemoteDescription(reoffer); 77 await pc2.setLocalDescription(); 78 await wait(2000); 79 }, 80 81 async function checkAudioMidSwap() { 82 const pc1 = new RTCPeerConnection(); 83 const pc2 = new RTCPeerConnection(); 84 85 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 86 pc1.addTrack(stream.getTracks()[0]); 87 pc2.addTrack(stream.getTracks()[0]); 88 89 await connect(pc1, pc2, 32000, "Initial connection"); 90 91 // Sadly, there's no way to tell the offerer to change the extmap. Other 92 // types of endpoint could conceivably do this, so we at least don't want 93 // to crash. 94 // TODO: Would be nice to be able to test this with an endpoint that 95 // actually changes the ids it uses. 96 const reoffer = await pc1.createOffer(); 97 const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid"); 98 const ssrcLevelId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); 99 reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", ssrcLevelId); 100 reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", midId); 101 info(`New reoffer: ${reoffer.sdp}`); 102 try { 103 await pc2.setRemoteDescription(reoffer); 104 ok(false, "sRD should fail when it attempts extension id remapping"); 105 } catch (e) { 106 ok(true, "sRD should fail when it attempts extension id remapping"); 107 } 108 }, 109 110 async function checkVideoMidSwap() { 111 const pc1 = new RTCPeerConnection(); 112 const pc2 = new RTCPeerConnection(); 113 114 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 115 pc1.addTrack(stream.getTracks()[0]); 116 pc2.addTrack(stream.getTracks()[0]); 117 118 await connect(pc1, pc2, 32000, "Initial connection"); 119 120 // Sadly, there's no way to tell the offerer to change the extmap. Other 121 // types of endpoint could conceivably do this, so we at least don't want 122 // to crash. 123 // TODO: Would be nice to be able to test this with an endpoint that 124 // actually changes the ids it uses. 125 const reoffer = await pc1.createOffer(); 126 const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid"); 127 const toffsetId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset"); 128 reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", toffsetId); 129 reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", midId); 130 info(`New reoffer: ${reoffer.sdp}`); 131 try { 132 await pc2.setRemoteDescription(reoffer); 133 ok(false, "sRD should fail when it attempts extension id remapping"); 134 } catch (e) { 135 ok(true, "sRD should fail when it attempts extension id remapping"); 136 } 137 }, 138 139 async function checkAudioIdReuse() { 140 const pc1 = new RTCPeerConnection(); 141 const pc2 = new RTCPeerConnection(); 142 143 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 144 pc1.addTrack(stream.getTracks()[0]); 145 pc2.addTrack(stream.getTracks()[0]); 146 147 await connect(pc1, pc2, 32000, "Initial connection"); 148 149 // Sadly, there's no way to tell the offerer to change the extmap. Other 150 // types of endpoint could conceivably do this, so we at least don't want 151 // to crash. 152 // TODO: Would be nice to be able to test this with an endpoint that 153 // actually changes the ids it uses. 154 const reoffer = await pc1.createOffer(); 155 // Change uri, but not the id, so the id now refers to foo. 156 reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo"); 157 info(`New reoffer: ${reoffer.sdp}`); 158 try { 159 await pc2.setRemoteDescription(reoffer); 160 ok(false, "sRD should fail when it attempts extension id remapping"); 161 } catch (e) { 162 ok(true, "sRD should fail when it attempts extension id remapping"); 163 } 164 }, 165 166 async function checkVideoIdReuse() { 167 const pc1 = new RTCPeerConnection(); 168 const pc2 = new RTCPeerConnection(); 169 170 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 171 pc1.addTrack(stream.getTracks()[0]); 172 pc2.addTrack(stream.getTracks()[0]); 173 174 await connect(pc1, pc2, 32000, "Initial connection"); 175 176 // Sadly, there's no way to tell the offerer to change the extmap. Other 177 // types of endpoint could conceivably do this, so we at least don't want 178 // to crash. 179 // TODO: Would be nice to be able to test this with an endpoint that 180 // actually changes the ids it uses. 181 const reoffer = await pc1.createOffer(); 182 // Change uri, but not the id, so the id now refers to foo. 183 reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", "foo"); 184 info(`New reoffer: ${reoffer.sdp}`); 185 try { 186 await pc2.setRemoteDescription(reoffer); 187 ok(false, "sRD should fail when it attempts extension id remapping"); 188 } catch (e) { 189 ok(true, "sRD should fail when it attempts extension id remapping"); 190 } 191 }, 192 193 // What happens when remote answer uses an extmap id, and then a remote 194 // reoffer tries to use the same id for something else? 195 async function checkAudioIdReuseOffererThenAnswerer() { 196 const pc1 = new RTCPeerConnection(); 197 const pc2 = new RTCPeerConnection(); 198 199 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 200 pc1.addTrack(stream.getTracks()[0]); 201 pc2.addTrack(stream.getTracks()[0]); 202 203 await connect(pc1, pc2, 32000, "Initial connection"); 204 205 const reoffer = await pc2.createOffer(); 206 // Change uri, but not the id, so the id now refers to foo. 207 reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo"); 208 info(`New reoffer: ${reoffer.sdp}`); 209 try { 210 await pc1.setRemoteDescription(reoffer); 211 ok(false, "sRD should fail when it attempts extension id remapping"); 212 } catch (e) { 213 ok(true, "sRD should fail when it attempts extension id remapping"); 214 } 215 }, 216 217 // What happens when a remote offer uses a different extmap id than the 218 // default? Does the answerer remember the new id in reoffers? 219 async function checkAudioIdReuseOffererThenAnswerer() { 220 const pc1 = new RTCPeerConnection(); 221 const pc2 = new RTCPeerConnection(); 222 223 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 224 pc1.addTrack(stream.getTracks()[0]); 225 pc2.addTrack(stream.getTracks()[0]); 226 227 // Negotiate, but change id for ssrc-audio-level to something pc2 would 228 // not typically use. 229 await pc1.setLocalDescription(); 230 const mungedOffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 12); 231 await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"}); 232 await pc2.setLocalDescription(); 233 234 const reoffer = await pc2.createOffer(); 235 is(getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"), "12"); 236 }, 237 238 async function checkAudioUnnegotiatedIdReuse1() { 239 const pc1 = new RTCPeerConnection(); 240 const pc2 = new RTCPeerConnection(); 241 242 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 243 pc1.addTrack(stream.getTracks()[0]); 244 pc2.addTrack(stream.getTracks()[0]); 245 246 // Negotiate, but remove ssrc-audio-level from answer 247 await pc1.setLocalDescription(); 248 const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); 249 await pc2.setRemoteDescription(pc1.localDescription); 250 await pc2.setLocalDescription(); 251 const answerNoExt = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); 252 await pc1.setRemoteDescription({sdp: answerNoExt, type: "answer"}); 253 254 // Renegotiate, and use the id that offerer used for ssrc-audio-level for 255 // something different (while making sure we don't use it twice) 256 await pc2.setLocalDescription(); 257 const mungedReoffer = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId); 258 const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); 259 await pc1.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"}); 260 }, 261 262 async function checkAudioUnnegotiatedIdReuse2() { 263 const pc1 = new RTCPeerConnection(); 264 const pc2 = new RTCPeerConnection(); 265 266 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 267 pc1.addTrack(stream.getTracks()[0]); 268 pc2.addTrack(stream.getTracks()[0]); 269 270 // Negotiate, but remove ssrc-audio-level from offer. pc2 has never seen 271 // |levelId| in extmap yet, but internally probably wants to use that for 272 // ssrc-audio-level 273 await pc1.setLocalDescription(); 274 const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); 275 const offerNoExt = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); 276 await pc2.setRemoteDescription({sdp: offerNoExt, type: "offer"}); 277 await pc2.setLocalDescription(); 278 await pc1.setRemoteDescription(pc2.localDescription); 279 280 // Renegotiate, but use |levelId| for something other than 281 // ssrc-audio-level. pc2 should not throw. 282 await pc1.setLocalDescription(); 283 const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId); 284 const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); 285 await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"}); 286 }, 287 288 async function checkAudioUnnegotiatedIdReuse3() { 289 const pc1 = new RTCPeerConnection(); 290 const pc2 = new RTCPeerConnection(); 291 292 const stream = await navigator.mediaDevices.getUserMedia({audio: true}); 293 pc1.addTrack(stream.getTracks()[0]); 294 pc2.addTrack(stream.getTracks()[0]); 295 296 // Negotiate, but replace ssrc-audio-level with something pc2 won't 297 // support in offer. 298 await pc1.setLocalDescription(); 299 const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); 300 const mungedOffer = replaceExtUri(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "fooba"); 301 await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"}); 302 await pc2.setLocalDescription(); 303 await pc1.setRemoteDescription(pc2.localDescription); 304 305 // Renegotiate, and use levelId for something pc2 _will_ support. 306 await pc1.setLocalDescription(); 307 const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId); 308 const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); 309 await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"}); 310 }, 311 312 ]; 313 314 runNetworkTest(async () => { 315 for (const test of tests) { 316 info(`Running test: ${test.name}`); 317 await test(); 318 info(`Done running test: ${test.name}`); 319 } 320 }); 321 322 </script> 323 </pre> 324 </body> 325 </html>