tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>