tor-browser

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

test_peerConnection_setParameters.html (18663B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <script type="application/javascript" src="pc.js"></script>
      5 </head>
      6 <body>
      7 <pre id="test">
      8 <script type="application/javascript">
      9 createHTML({
     10  bug: "1230184",
     11  title: "Set parameters on sender",
     12  visible: true
     13 });
     14 
     15 const simulcastOffer = `v=0
     16 o=- 3840232462471583827 0 IN IP4 127.0.0.1
     17 s=-
     18 t=0 0
     19 a=group:BUNDLE 0
     20 a=msid-semantic: WMS
     21 m=video 9 UDP/TLS/RTP/SAVPF 96
     22 c=IN IP4 0.0.0.0
     23 a=rtcp:9 IN IP4 0.0.0.0
     24 a=ice-ufrag:Li6+
     25 a=ice-pwd:3C05CTZBRQVmGCAq7hVasHlT
     26 a=ice-options:trickle
     27 a=fingerprint:sha-256 5B:D3:8E:66:0E:7D:D3:F3:8E:E6:80:28:19:FC:55:AD:58:5D:B9:3D:A8:DE:45:4A:E7:87:02:F8:3C:0B:3B:B3
     28 a=setup:actpass
     29 a=mid:0
     30 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
     31 a=recvonly
     32 a=rtcp-mux
     33 a=rtpmap:96 VP8/90000
     34 a=rtcp-fb:96 goog-remb
     35 a=rtcp-fb:96 transport-cc
     36 a=rtcp-fb:96 ccm fir
     37 a=rid:foo recv
     38 a=rid:bar recv
     39 a=simulcast:recv foo;bar
     40 `;
     41 
     42 function buildMaximumSendEncodings() {
     43  const sendEncodings = [];
     44  while (true) {
     45    // isDeeply does not see identical string primitives and String objects
     46    // as the same, so we make this a string primitive.
     47    sendEncodings.push({rid: `${sendEncodings.length}`});
     48    const pc = new RTCPeerConnection();
     49    const {sender} = pc.addTransceiver('video', {sendEncodings});
     50    const {encodings} = sender.getParameters();
     51    if (encodings.length < sendEncodings.length) {
     52      sendEncodings.pop();
     53      return sendEncodings;
     54    }
     55  }
     56 }
     57 
     58 async function queueAWebrtcTask() {
     59  const pc = new RTCPeerConnection();
     60  pc.addTransceiver('audio');
     61  await new Promise(r => pc.onnegotiationneeded = r);
     62  pc.close();
     63 }
     64 
     65 // setParameters is mostly tested in wpt, but we test a few
     66 // implementation-specific things here. Other mochitests check whether the
     67 // set parameters actually have the desired effect on the media streams.
     68 const tests = [
     69 
     70  // wpt currently does not assume support for 3 encodings, which limits the
     71  // effectiveness of its powers-of-2 test (since it can test only for 1 and 2)
     72  async function checkScaleResolutionDownByAutoFillPowersOf2() {
     73    const pc = new RTCPeerConnection();
     74    const {sender} = pc.addTransceiver('video', {
     75      sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
     76    });
     77    const {encodings} = sender.getParameters();
     78    const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
     79    isDeeply(scaleValues, [4, 2, 1]);
     80  },
     81 
     82  // wpt currently does not assume support for 3 encodings, which limits the
     83  // effectiveness of its fill-with-1 test
     84  async function checkScaleResolutionDownByAutoFillWith1() {
     85    const pc = new RTCPeerConnection();
     86    const {sender} = pc.addTransceiver('video', {
     87      sendEncodings: [
     88        {rid: "0"},{rid: "1", scaleResolutionDownBy: 3},{rid: "2"}
     89      ]
     90    });
     91    const {encodings} = sender.getParameters();
     92    const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
     93    isDeeply(scaleValues, [1, 3, 1]);
     94  },
     95 
     96  async function checkVideoEncodingLimit() {
     97    const pc = new RTCPeerConnection();
     98    const maxSendEncodings = buildMaximumSendEncodings();
     99    const sendEncodings = maxSendEncodings.concat({rid: "a"});
    100    const {sender} = pc.addTransceiver('video', {sendEncodings});
    101    const {encodings} = sender.getParameters();
    102 
    103    const rids = encodings.map(({rid}) => rid);
    104    const expectedRids = maxSendEncodings.map(({rid}) => rid);
    105    isDeeply(rids, expectedRids);
    106 
    107    const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
    108    const expectedScaleValues = [];
    109    let scale = 1;
    110    while (expectedScaleValues.length < maxSendEncodings.length) {
    111      expectedScaleValues.push(scale);
    112      scale *= 2;
    113    }
    114    isDeeply(scaleValues, expectedScaleValues.reverse());
    115  },
    116 
    117  async function checkScaleDownByInTrimmedEncoding() {
    118    const pc = new RTCPeerConnection();
    119    const maxSendEncodings = buildMaximumSendEncodings();
    120    const sendEncodings = maxSendEncodings.concat({rid: "a", scaleResolutionDownBy: 3});
    121    const {sender} = pc.addTransceiver('video', {sendEncodings});
    122    const {encodings} = sender.getParameters();
    123    const rids = encodings.map(({rid}) => rid);
    124    const expectedRids = maxSendEncodings.map(({rid}) => rid);
    125    isDeeply(rids, expectedRids);
    126    const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
    127    const expectedScaleValues = maxSendEncodings.map(() => 1);
    128    isDeeply(scaleValues, expectedScaleValues);
    129  },
    130 
    131  async function checkLibwebrtcRidLengthLimit() {
    132    const pc = new RTCPeerConnection();
    133    try {
    134      pc.addTransceiver('video', {
    135        sendEncodings: [{rid: "wibblywobblyjeremybearimy"}]}
    136      );
    137      ok(false, "Rid should be too long for libwebrtc!");
    138    } catch (e) {
    139      is(e.name, "TypeError",
    140        "Rid that is too long for libwebrtc should result in a TypeError");
    141    }
    142  },
    143 
    144  async function checkErrorsInTrimmedEncodings() {
    145    const pc = new RTCPeerConnection();
    146    const maxSendEncodings = buildMaximumSendEncodings();
    147    try {
    148      const sendEncodings = maxSendEncodings.concat({rid: "foo-bar"});
    149      pc.addTransceiver('video', { sendEncodings });
    150      ok(false, "Should throw due to invalid rid characters");
    151    } catch (e) {
    152      is(e.name, "TypeError")
    153    }
    154    try {
    155      const sendEncodings = maxSendEncodings.concat({rid: "wibblywobblyjeremybearimy"});
    156      pc.addTransceiver('video', { sendEncodings });
    157      ok(false, "Should throw because rid too long");
    158    } catch (e) {
    159      is(e.name, "TypeError")
    160    }
    161    try {
    162      const sendEncodings = maxSendEncodings.concat({scaleResolutionDownBy: 2});
    163      pc.addTransceiver('video', { sendEncodings });
    164      ok(false, "Should throw due to missing rid");
    165    } catch (e) {
    166      is(e.name, "TypeError")
    167    }
    168    try {
    169      const sendEncodings = maxSendEncodings.concat(maxSendEncodings[0]);
    170      pc.addTransceiver('video', { sendEncodings });
    171      ok(false, "Should throw due to duplicate rid");
    172    } catch (e) {
    173      is(e.name, "TypeError")
    174    }
    175    try {
    176      const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, scaleResolutionDownBy: 0});
    177      pc.addTransceiver('video', { sendEncodings });
    178      ok(false, "Should throw due to invalid scaleResolutionDownBy");
    179    } catch (e) {
    180      is(e.name, "RangeError")
    181    }
    182    try {
    183      const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, maxFramerate: -1});
    184      pc.addTransceiver('video', { sendEncodings });
    185      ok(false, "Should throw due to invalid maxFramerate");
    186    } catch (e) {
    187      is(e.name, "RangeError")
    188    }
    189  },
    190 
    191  async function checkCompatModeUnicastSetParametersAllowsSimulcastOffer() {
    192    await pushPrefs(
    193      ["media.peerconnection.allow_old_setParameters", true]);
    194    const pc1 = new RTCPeerConnection();
    195    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    196    const sender = pc1.addTrack(stream.getTracks()[0]);
    197    const parameters = sender.getParameters();
    198    parameters.encodings[0].scaleResolutionDownBy = 3.0;
    199    await sender.setParameters(parameters);
    200 
    201    await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
    202 
    203    const {encodings} = sender.getParameters();
    204    const rids = encodings.map(({rid}) => rid);
    205    isDeeply(rids, ["foo", "bar"]);
    206    is(encodings[0].scaleResolutionDownBy, 2.0);
    207    is(encodings[1].scaleResolutionDownBy, 1.0);
    208  },
    209 
    210  async function checkCompatModeUnicastSetParametersInterruptAllowsSimulcastOffer() {
    211    await pushPrefs(
    212      ["media.peerconnection.allow_old_setParameters", true]);
    213    const pc1 = new RTCPeerConnection();
    214    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    215    const sender = pc1.addTrack(stream.getTracks()[0]);
    216    const parameters = sender.getParameters();
    217    parameters.encodings[0].scaleResolutionDownBy = 3.0;
    218 
    219    const offerDone = pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
    220    await sender.setParameters(parameters);
    221    await offerDone;
    222 
    223    const {encodings} = sender.getParameters();
    224    const rids = encodings.map(({rid}) => rid);
    225    isDeeply(rids, ["foo", "bar"]);
    226    is(encodings[0].scaleResolutionDownBy, 2.0);
    227    is(encodings[1].scaleResolutionDownBy, 1.0);
    228  },
    229 
    230  async function checkCompatModeSimulcastSetParametersSetsSimulcastEnvelope() {
    231    await pushPrefs(
    232      ["media.peerconnection.allow_old_setParameters", true]);
    233    const pc1 = new RTCPeerConnection();
    234    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    235    const sender = pc1.addTrack(stream.getTracks()[0]);
    236    const parameters = sender.getParameters();
    237    parameters.encodings[0].rid = "1";
    238    parameters.encodings.push({rid: "2"});
    239    await sender.setParameters(parameters);
    240 
    241    await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
    242 
    243    const {encodings} = sender.getParameters();
    244    const rids = encodings.map(({rid}) => rid);
    245    // No overlap in rids -> unicast
    246    isDeeply(rids, [undefined]);
    247  },
    248 
    249  async function checkCompatModeSimulcastSetParametersRacesLocalUnicastOffer() {
    250    await pushPrefs(
    251      ["media.peerconnection.allow_old_setParameters", true]);
    252    const pc1 = new RTCPeerConnection();
    253    const pc2 = new RTCPeerConnection();
    254    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    255    const sender = pc1.addTrack(stream.getTracks()[0]);
    256    // unicast offer
    257    const offer = await pc1.createOffer();
    258    const aTask = queueAWebrtcTask();
    259    const sldPromise =  pc1.setLocalDescription(offer);
    260 
    261    // Right now, we have aTask queued. The success task for sLD is not queued
    262    // yet, because Firefox performs the initial steps on the microtask queue,
    263    // which we have not allowed to run yet. Awaiting aTask will first clear
    264    // the microtask queue, then run the task queue until aTask is finished.
    265    // That _should_ result in the success task for sLD(offer) being queued.
    266    await aTask;
    267 
    268    const parameters = sender.getParameters();
    269    parameters.encodings[0].rid = "foo";
    270    parameters.encodings.push({rid: "bar"});
    271    // simulcast setParameters; the task to update [[SendEncodings]] should be
    272    // queued after the success task for sLD(offer)
    273    await sender.setParameters(parameters);
    274    await sldPromise;
    275 
    276    const {encodings} = sender.getParameters();
    277    const rids = encodings.map(({rid}) => rid);
    278    // Compat mode lets this slide, but won't try to negotiate it since we've
    279    // already applied a unicast local offer.
    280    isDeeply(rids, ["foo", "bar"]);
    281 
    282    // Let negotiation finish, so we can generate a new offer
    283    await pc2.setRemoteDescription(pc1.localDescription);
    284    await pc2.setLocalDescription();
    285    await pc1.setRemoteDescription(pc2.localDescription);
    286 
    287    const reoffer = await pc1.createOffer();
    288    ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast");
    289  },
    290 
    291  async function checkCompatModeSimulcastSetParametersRacesRemoteOffer() {
    292    await pushPrefs(
    293      ["media.peerconnection.allow_old_setParameters", true]);
    294    const pc1 = new RTCPeerConnection();
    295    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    296    const sender = pc1.addTrack(stream.getTracks()[0]);
    297    const parameters = sender.getParameters();
    298    parameters.encodings[0].rid = "foo";
    299    parameters.encodings.push({rid: "bar"});
    300    const p = sender.setParameters(parameters);
    301    await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
    302    await p;
    303    const answer = await pc1.createAnswer();
    304 
    305    const {encodings} = sender.getParameters();
    306    const rids = encodings.map(({rid}) => rid);
    307    isDeeply(rids, ["foo", "bar"]);
    308    ok(answer.sdp.includes("a=simulcast:send foo;bar"), "answer should be simulcast");
    309  },
    310 
    311  async function checkCompatModeSimulcastSetParametersRacesLocalAnswer() {
    312    await pushPrefs(
    313      ["media.peerconnection.allow_old_setParameters", true]);
    314    // We do an initial negotiation, and while the local answer is pending,
    315    // perform a setParameters on a not-yet-negotiated video sender. The intent
    316    // here is to have the success task for sLD(answer) run while the
    317    // setParameters is pending.
    318    const pc1 = new RTCPeerConnection();
    319    const pc2 = new RTCPeerConnection();
    320 
    321    const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
    322    // We use this later on, but set it up now so we don't inadvertently
    323    // crank the event loop more than we intend below.
    324    const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
    325    pc2.addTrack(audioStream.getTracks()[0]);
    326    await pc2.setLocalDescription();
    327    await pc1.setRemoteDescription(pc2.localDescription);
    328    const answer = await pc1.createAnswer();
    329    const aTask = queueAWebrtcTask();
    330    const sldPromise = pc1.setLocalDescription(answer);
    331 
    332    // Right now, we have aTask queued. The success task for sLD is not queued
    333    // yet, because Firefox performs the initial steps on the microtask queue,
    334    // which we have not allowed to run yet. Awaiting aTask will first clear
    335    // the microtask queue, then run the task queue until aTask is finished.
    336    // That _should_ result in the success task for sLD(answer) being queued.
    337    await aTask;
    338 
    339    // The success task for sLD(answer) should be queued now. Don't relinquish
    340    // the event loop!
    341 
    342    // New sender that has nothing to do with the negotiation in progress.
    343    const sender = pc1.addTrack(videoStream.getTracks()[0]);
    344    const parameters = sender.getParameters();
    345    parameters.encodings[0].rid = "foo";
    346    parameters.encodings.push({rid: "bar"});
    347 
    348    // We have not relinquished the event loop, so the sLD(answer) should still
    349    // be queued. The task that updates [[SendEncodings]] (from setParameters)
    350    // should be queued behind it. Let them both run.
    351    await sender.setParameters(parameters);
    352    await sldPromise;
    353 
    354    const offer = await pc1.createOffer();
    355 
    356    const {encodings} = sender.getParameters();
    357    const rids = encodings.map(({rid}) => rid);
    358    isDeeply(rids, ["foo", "bar"]);
    359    ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast");
    360  },
    361 
    362  async function checkCompatModeSimulcastSetParametersRacesRemoteAnswer() {
    363    await pushPrefs(
    364      ["media.peerconnection.allow_old_setParameters", true]);
    365    // We do an initial negotiation, and while the remote answer is pending,
    366    // perform a setParameters on a not-yet-negotiated video sender. The intent
    367    // here is to have the success task for sRD(answer) run while the
    368    // setParameters is pending.
    369    const pc1 = new RTCPeerConnection();
    370    const pc2 = new RTCPeerConnection();
    371 
    372    const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
    373    // We use this later on, but set it up now so we don't inadvertently
    374    // crank the event loop more than we intend below.
    375    const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
    376    pc1.addTrack(audioStream.getTracks()[0]);
    377    await pc1.setLocalDescription();
    378    await pc2.setRemoteDescription(pc1.localDescription);
    379    await pc2.setLocalDescription();
    380    const aTask = queueAWebrtcTask();
    381    const srdPromise = pc1.setRemoteDescription(pc2.localDescription);
    382 
    383    // Right now, we have aTask queued. The success task for sRD is not queued
    384    // yet, because Firefox performs the initial steps on the microtask queue,
    385    // which we have not allowed to run yet. Awaiting aTask will first clear
    386    // the microtask queue, then run the task queue until aTask is finished.
    387    // That _should_ result in the success task for sRD(answer) being queued.
    388    await aTask;
    389 
    390    // The success task for sRD(answer) should be queued now. Don't relinquish
    391    // the event loop!
    392 
    393    const sender = pc1.addTrack(videoStream.getTracks()[0]);
    394    const parameters = sender.getParameters();
    395    parameters.encodings[0].rid = "foo";
    396    parameters.encodings.push({rid: "bar"});
    397 
    398    // We have not relinquished the event loop, so the sRD(answer) should still
    399    // be queued. The task that updates [[SendEncodings]] (from setParameters)
    400    // should be queued behind it. Let them both run.
    401    await sender.setParameters(parameters);
    402    await srdPromise;
    403 
    404    const offer = await pc1.createOffer();
    405 
    406    const {encodings} = sender.getParameters();
    407    const rids = encodings.map(({rid}) => rid);
    408    isDeeply(rids, ["foo", "bar"]);
    409    ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast");
    410  },
    411 
    412  async function checkCompatModeSimulcastRidlessSetParametersRacesLocalOffer() {
    413    await pushPrefs(
    414      ["media.peerconnection.allow_old_setParameters", true]);
    415    const pc1 = new RTCPeerConnection();
    416    const pc2 = new RTCPeerConnection();
    417    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    418    const sender = pc1.addTrack(stream.getTracks()[0]);
    419    // unicast offer
    420    const aTask = queueAWebrtcTask();
    421    const sldPromise =  pc1.setLocalDescription();
    422 
    423    // Right now, we have aTask queued. The success task for sLD is not queued
    424    // yet, because Firefox performs the initial steps on the microtask queue,
    425    // which we have not allowed to run yet. Awaiting aTask will first clear
    426    // the microtask queue, then run the task queue until aTask is finished.
    427    // That _should_ result in the success task for sLD(offer) being queued.
    428    await aTask;
    429 
    430    // simulcast setParameters; the task to update [[SendEncodings]] should be
    431    // queued after the success task for sLD(offer)
    432    try {
    433      await sender.setParameters({"encodings": [{}, {}]});
    434      ok(false, "setParameters with two ridless encodings should fail");
    435    } catch (e) {
    436      ok(true, "setParameters with two ridless encodings should fail");
    437    }
    438    await sldPromise;
    439 
    440    const {encodings} = sender.getParameters();
    441    const rids = encodings.map(({rid}) => rid);
    442    // Compat mode lets this slide, but won't try to negotiate it since we've
    443    // already applied a unicast local offer.
    444    isDeeply(rids, [undefined]);
    445 
    446    // Let negotiation finish, so we can generate a new offer
    447    await pc2.setRemoteDescription(pc1.localDescription);
    448    await pc2.setLocalDescription();
    449    await pc1.setRemoteDescription(pc2.localDescription);
    450 
    451    const reoffer = await pc1.createOffer();
    452    ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast");
    453  },
    454 
    455 ];
    456 
    457 runNetworkTest(async () => {
    458  await pushPrefs(
    459    ["media.peerconnection.allow_old_setParameters", false]);
    460  for (const test of tests) {
    461    info(`Running test: ${test.name}`);
    462    await test();
    463    info(`Done running test: ${test.name}`);
    464  }
    465 });
    466 
    467 </script>
    468 </pre>
    469 </body>
    470 </html>