tor-browser

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

RTCPeerConnection-setRemoteDescription-offer.html (16122B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCPeerConnection.prototype.setRemoteDescription - offer</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="RTCPeerConnection-helper.js"></script>
      7 <script src="/webrtc/third_party/sdp/sdp.js"></script>
      8 <script>
      9  'use strict';
     10 
     11  // The following helper functions are called from RTCPeerConnection-helper.js:
     12  //   assert_session_desc_similar()
     13  //   generateAudioReceiveOnlyOffer
     14 
     15  /*
     16    4.3.2.  Interface Definition
     17      [Constructor(optional RTCConfiguration configuration)]
     18      interface RTCPeerConnection : EventTarget {
     19        Promise<void>                      setRemoteDescription(
     20            RTCSessionDescriptionInit description);
     21 
     22        readonly attribute RTCSessionDescription? remoteDescription;
     23        readonly attribute RTCSessionDescription? currentRemoteDescription;
     24        readonly attribute RTCSessionDescription? pendingRemoteDescription;
     25        ...
     26      };
     27 
     28    4.6.2.  RTCSessionDescription Class
     29      dictionary RTCSessionDescriptionInit {
     30        required RTCSdpType type;
     31                 DOMString  sdp = "";
     32      };
     33 
     34    4.6.1.  RTCSdpType
     35      enum RTCSdpType {
     36        "offer",
     37        "pranswer",
     38        "answer",
     39        "rollback"
     40      };
     41   */
     42 
     43  /*
     44    4.3.1.6.  Set the RTCSessionSessionDescription
     45      2.2.3.  Otherwise, if description is set as a remote description, then run one of
     46              the following steps:
     47        - If description is of type "offer", set connection.pendingRemoteDescription
     48          attribute to description and signaling state to have-remote-offer.
     49   */
     50 
     51  promise_test(t => {
     52    const pc1 = new RTCPeerConnection();
     53    t.add_cleanup(() => pc1.close());
     54    pc1.createDataChannel('datachannel');
     55 
     56    const pc2 = new RTCPeerConnection();
     57    t.add_cleanup(() => pc2.close());
     58 
     59    const states = [];
     60    pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
     61 
     62    return pc1.createOffer()
     63     .then(offer => {
     64      return pc2.setRemoteDescription(offer)
     65      .then(() => {
     66        assert_equals(pc2.signalingState, 'have-remote-offer');
     67        assert_session_desc_similar(pc2.remoteDescription, offer);
     68        assert_session_desc_similar(pc2.pendingRemoteDescription, offer);
     69        assert_equals(pc2.currentRemoteDescription, null);
     70 
     71        assert_array_equals(states, ['have-remote-offer']);
     72      });
     73    });
     74  }, 'setRemoteDescription with valid offer should succeed');
     75 
     76  promise_test(t => {
     77    const pc1 = new RTCPeerConnection();
     78    t.add_cleanup(() => pc1.close());
     79    pc1.createDataChannel('datachannel');
     80 
     81    const pc2 = new RTCPeerConnection();
     82    t.add_cleanup(() => pc2.close());
     83 
     84    const states = [];
     85    pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
     86 
     87    return pc1.createOffer()
     88    .then(offer => {
     89      return pc2.setRemoteDescription(offer)
     90      .then(() => pc2.setRemoteDescription(offer))
     91      .then(() => {
     92        assert_equals(pc2.signalingState, 'have-remote-offer');
     93        assert_session_desc_similar(pc2.remoteDescription, offer);
     94        assert_session_desc_similar(pc2.pendingRemoteDescription, offer);
     95        assert_equals(pc2.currentRemoteDescription, null);
     96 
     97        assert_array_equals(states, ['have-remote-offer']);
     98      });
     99    });
    100  }, 'setRemoteDescription multiple times should succeed');
    101 
    102  promise_test(t => {
    103    const pc1 = new RTCPeerConnection();
    104    t.add_cleanup(() => pc1.close());
    105    pc1.createDataChannel('datachannel');
    106 
    107    const pc2 = new RTCPeerConnection();
    108    t.add_cleanup(() => pc2.close());
    109 
    110    const states = [];
    111    pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
    112 
    113    return pc1.createOffer()
    114    .then(offer1 => {
    115      return pc1.setLocalDescription(offer1)
    116       .then(()=> {
    117        return generateAudioReceiveOnlyOffer(pc1)
    118        .then(offer2 => {
    119          assert_session_desc_not_similar(offer1, offer2);
    120 
    121          return pc2.setRemoteDescription(offer1)
    122          .then(() => pc2.setRemoteDescription(offer2))
    123          .then(() => {
    124            assert_equals(pc2.signalingState, 'have-remote-offer');
    125            assert_session_desc_similar(pc2.remoteDescription, offer2);
    126            assert_session_desc_similar(pc2.pendingRemoteDescription, offer2);
    127            assert_equals(pc2.currentRemoteDescription, null);
    128 
    129            assert_array_equals(states, ['have-remote-offer']);
    130          });
    131        });
    132      });
    133    });
    134  }, 'setRemoteDescription multiple times with different offer should succeed');
    135 
    136  /*
    137    4.3.1.6.  Set the RTCSessionSessionDescription
    138      2.1.4.  If the content of description is not valid SDP syntax, then reject p with
    139              an RTCError (with errorDetail set to "sdp-syntax-error" and the
    140              sdpLineNumber attribute set to the line number in the SDP where the syntax
    141              error was detected) and abort these steps.
    142   */
    143  promise_test(t => {
    144    const pc = new RTCPeerConnection();
    145 
    146    t.add_cleanup(() => pc.close());
    147 
    148    return pc.setRemoteDescription({
    149      type: 'offer',
    150      sdp: 'Invalid SDP'
    151    })
    152    .then(() => {
    153      assert_unreached('Expect promise to be rejected');
    154    }, err => {
    155      assert_equals(err.errorDetail, 'sdp-syntax-error',
    156        'Expect error detail field to set to sdp-syntax-error');
    157 
    158      assert_true(err instanceof RTCError,
    159        'Expect err to be instance of RTCError');
    160    });
    161  }, 'setRemoteDescription(offer) with invalid SDP should reject with RTCError');
    162 
    163  promise_test(async t => {
    164    const pc1 = new RTCPeerConnection();
    165    const pc2 = new RTCPeerConnection();
    166    t.add_cleanup(() => pc1.close());
    167    t.add_cleanup(() => pc2.close());
    168    await pc1.setLocalDescription(await pc1.createOffer());
    169    await pc1.setRemoteDescription(await pc2.createOffer());
    170    assert_equals(pc1.signalingState, 'have-remote-offer');
    171  }, 'setRemoteDescription(offer) from have-local-offer should roll back and succeed');
    172 
    173  promise_test(async t => {
    174    const pc1 = new RTCPeerConnection();
    175    const pc2 = new RTCPeerConnection();
    176    t.add_cleanup(() => pc1.close());
    177    t.add_cleanup(() => pc2.close());
    178    await pc1.setLocalDescription(await pc1.createOffer());
    179    const p = pc1.setRemoteDescription(await pc2.createOffer());
    180    await new Promise(r => pc1.onsignalingstatechange = r);
    181    assert_equals(pc1.signalingState, 'stable');
    182    assert_equals(pc1.pendingLocalDescription, null);
    183    assert_equals(pc1.pendingRemoteDescription, null);
    184    await new Promise(r => pc1.onsignalingstatechange = r);
    185    assert_equals(pc1.signalingState, 'have-remote-offer');
    186    assert_equals(pc1.pendingLocalDescription, null);
    187    assert_equals(pc1.pendingRemoteDescription.type, 'offer');
    188    await p;
    189  }, 'setRemoteDescription(offer) from have-local-offer fires signalingstatechange twice');
    190 
    191  promise_test(async t => {
    192    const pc1 = new RTCPeerConnection();
    193    t.add_cleanup(() => pc1.close());
    194    const pc2 = new RTCPeerConnection();
    195    t.add_cleanup(() => pc2.close());
    196 
    197    pc1.addTransceiver('audio', { direction: 'recvonly' });
    198    const srdPromise = pc2.setRemoteDescription(await pc1.createOffer());
    199 
    200    assert_equals(pc2.signalingState, "stable", "signalingState should not be set synchronously after a call to sRD");
    201 
    202    assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should not be set synchronously after a call to sRD");
    203    assert_equals(pc2.currentRemoteDescription, null, "currentRemoteDescription should not be set synchronously after a call to sRD");
    204 
    205    const statePromise = new Promise(resolve => {
    206      pc2.onsignalingstatechange = () => {
    207        resolve(pc2.signalingState);
    208      }
    209    });
    210 
    211    const raceValue = await Promise.race([statePromise, srdPromise]);
    212    assert_equals(raceValue, "have-remote-offer", "signalingstatechange event should fire before sRD resolves");
    213    assert_not_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event");
    214    assert_equals(pc2.pendingRemoteDescription.type, "offer");
    215    assert_equals(pc2.pendingRemoteDescription, pc2.remoteDescription);
    216    assert_equals(pc2.currentRemoteDescription, null, "currentRemoteDescription should not be set after a call to sRD(offer)");
    217 
    218    await srdPromise;
    219  }, "setRemoteDescription(offer) in stable should update internal state with a queued task, in the right order");
    220 
    221  promise_test(async t => {
    222    const pc1 = new RTCPeerConnection();
    223    t.add_cleanup(() => pc1.close());
    224    const pc2 = new RTCPeerConnection();
    225    t.add_cleanup(() => pc2.close());
    226 
    227    pc2.addTransceiver('audio', { direction: 'recvonly' });
    228    await pc2.setLocalDescription(await pc2.createOffer());
    229 
    230    // Implicit rollback!
    231    pc1.addTransceiver('audio', { direction: 'recvonly' });
    232    const srdPromise = pc2.setRemoteDescription(await pc1.createOffer());
    233 
    234    assert_equals(pc2.signalingState, "have-local-offer", "signalingState should not be set synchronously after a call to sRD");
    235 
    236    assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should not be set synchronously after a call to sRD");
    237    assert_not_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should not be set synchronously after a call to sRD");
    238    assert_equals(pc2.pendingLocalDescription.type, "offer");
    239    assert_equals(pc2.pendingLocalDescription, pc2.localDescription);
    240 
    241    // First, we should go through stable (the implicit rollback part)
    242    const stablePromise = new Promise(resolve => {
    243      pc2.onsignalingstatechange = () => {
    244        resolve(pc2.signalingState);
    245      }
    246    });
    247 
    248    let raceValue = await Promise.race([stablePromise, srdPromise]);
    249    assert_equals(raceValue, "stable", "signalingstatechange event should fire before sRD resolves");
    250    assert_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should be updated before the signalingstatechange event");
    251    assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event");
    252 
    253    const haveRemoteOfferPromise = new Promise(resolve => {
    254      pc2.onsignalingstatechange = () => {
    255        resolve(pc2.signalingState);
    256      }
    257    });
    258 
    259    raceValue = await Promise.race([haveRemoteOfferPromise, srdPromise]);
    260    assert_equals(raceValue, "have-remote-offer", "signalingstatechange event should fire before sRD resolves");
    261    assert_not_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event");
    262    assert_equals(pc2.pendingRemoteDescription.type, "offer");
    263    assert_equals(pc2.pendingRemoteDescription, pc2.remoteDescription);
    264    assert_equals(pc2.pendingRemoteDescription, pc2.pendingRemoteDescription);
    265    assert_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should be updated before the signalingstatechange event");
    266 
    267    await srdPromise;
    268  }, "setRemoteDescription(offer) in have-local-offer should update internal state with a queued task, in the right order");
    269 
    270  promise_test(async t => {
    271    const pc1 = new RTCPeerConnection();
    272    const pc2 = new RTCPeerConnection();
    273    t.add_cleanup(() => pc1.close());
    274    t.add_cleanup(() => pc2.close());
    275    await pc1.setLocalDescription(await pc1.createOffer());
    276    const offer = await pc2.createOffer();
    277    const p1 = pc1.setLocalDescription({type: 'rollback'});
    278    await new Promise(r => pc1.onsignalingstatechange = r);
    279    assert_equals(pc1.signalingState, 'stable');
    280    const p2 = pc1.addIceCandidate();
    281    const p3 = pc1.setRemoteDescription(offer);
    282    await promise_rejects_dom(t, 'InvalidStateError', p2);
    283    await p1;
    284    await p3;
    285    assert_equals(pc1.signalingState, 'have-remote-offer');
    286  }, 'Naive rollback approach is not glare-proof (control)');
    287 
    288  promise_test(async t => {
    289    const pc1 = new RTCPeerConnection();
    290    const pc2 = new RTCPeerConnection();
    291    t.add_cleanup(() => pc1.close());
    292    t.add_cleanup(() => pc2.close());
    293    await pc1.setLocalDescription(await pc1.createOffer());
    294    const p = pc1.setRemoteDescription(await pc2.createOffer());
    295    await new Promise(r => pc1.onsignalingstatechange = r);
    296    assert_equals(pc1.signalingState, 'stable');
    297    await pc1.addIceCandidate();
    298    await p;
    299    assert_equals(pc1.signalingState, 'have-remote-offer');
    300  }, 'setRemoteDescription(offer) from have-local-offer is glare-proof');
    301 
    302  promise_test(async t => {
    303    const pc1 = new RTCPeerConnection();
    304    const pc2 = new RTCPeerConnection();
    305    t.add_cleanup(() => pc1.close());
    306    t.add_cleanup(() => pc2.close());
    307    await pc1.setLocalDescription(await pc1.createOffer());
    308    const statePromise = new Promise(r => pc1.onsignalingstatechange = r);
    309    // SRD with invalid SDP causes rollback.
    310    const p = pc1.setRemoteDescription({type: 'offer', sdp: 'Invalid SDP'});
    311    // Ensure that p is eventually awaited for
    312    t.add_cleanup(async () => Promise.allSettled([p]));
    313    // Ensure that the test will fail rather than timing out if state
    314    // does not change.
    315    const timeoutPromise = new Promise(
    316    (resolve, reject) => t.step_timeout(reject, 1000));
    317    try {
    318      await Promise.any(statePromise, timeoutPromise);
    319    } catch (error) {
    320      assert_unreached('State should have changed');
    321    }
    322    assert_equals(pc1.signalingState, 'stable');
    323    assert_equals(pc1.pendingLocalDescription, null);
    324    assert_equals(pc1.pendingRemoteDescription, null);
    325    await promise_rejects_dom(t, 'RTCError', p);
    326  }, 'setRemoteDescription(invalidOffer) from have-local-offer does not undo rollback');
    327 
    328  promise_test(async t => {
    329    const pc1 = new RTCPeerConnection();
    330    t.add_cleanup(() => pc1.close());
    331    const pc2 = new RTCPeerConnection();
    332    t.add_cleanup(() => pc2.close());
    333    pc1.addTransceiver('video');
    334    const offer = await pc1.createOffer();
    335    await pc2.setRemoteDescription(offer);
    336    assert_equals(pc2.getTransceivers().length, 1);
    337    await pc2.setRemoteDescription(offer);
    338    assert_equals(pc2.getTransceivers().length, 1);
    339    await pc1.setLocalDescription(offer);
    340    const answer = await pc2.createAnswer();
    341    await pc2.setLocalDescription(answer);
    342    await pc1.setRemoteDescription(answer);
    343  }, 'repeated sRD(offer) works');
    344 
    345  promise_test(async t => {
    346    const pc1 = new RTCPeerConnection();
    347    t.add_cleanup(() => pc1.close());
    348    const pc2 = new RTCPeerConnection();
    349    t.add_cleanup(() => pc2.close());
    350    pc1.addTransceiver('video');
    351    await exchangeOfferAnswer(pc1, pc2);
    352    await waitForIceGatheringState(pc1, ['complete']);
    353    await exchangeOfferAnswer(pc1, pc2);
    354    await waitForIceStateChange(pc2, ['connected', 'completed']);
    355  }, 'sRD(reoffer) with candidates and without trickle works');
    356 
    357  promise_test(async t => {
    358    const pc1 = new RTCPeerConnection();
    359    t.add_cleanup(() => pc1.close());
    360    const pc2 = new RTCPeerConnection();
    361    t.add_cleanup(() => pc2.close());
    362    pc1.addTransceiver('video');
    363    const offer = await pc1.createOffer();
    364    const srdPromise = pc2.setRemoteDescription(offer);
    365    assert_equals(pc2.getTransceivers().length, 0);
    366    await srdPromise;
    367    assert_equals(pc2.getTransceivers().length, 1);
    368  }, 'Transceivers added by sRD(offer) should not show up until sRD resolves');
    369 
    370  promise_test(async t => {
    371    const pc1 = new RTCPeerConnection();
    372    const pc2 = new RTCPeerConnection();
    373    t.add_cleanup(() => pc1.close());
    374    t.add_cleanup(() => pc2.close());
    375    pc1.addTransceiver('video');
    376    const description = await pc1.createOffer();
    377    const sections = SDPUtils.splitSections(description.sdp);
    378    // Compose SDP with a duplicated media section (equal MSID lines)
    379    // This is not permitted according to RFC 8830 section 2.
    380    const sdp = sections[0] + sections[1] + sections[1].replace('a=mid:', 'a=mid:unique');
    381    const p = pc2.setRemoteDescription({type: 'offer', sdp: sdp});
    382    await promise_rejects_dom(t, 'OperationError', p);
    383  }, 'setRemoteDescription(section with duplicate msid) rejects');
    384 
    385 </script>