tor-browser

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

RTCPeerConnection-connectionState.https.html (12147B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCPeerConnection.prototype.connectionState</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="RTCPeerConnection-helper.js"></script>
      7 <script>
      8  'use strict';
      9  // Test is based on the following editor draft:
     10  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.htm
     11 
     12  // The following helper functions are called from RTCPeerConnection-helper.js:
     13  // exchangeIceCandidates
     14  // exchangeOfferAnswer
     15 
     16  /*
     17    4.3.2.  Interface Definition
     18      interface RTCPeerConnection : EventTarget {
     19        ...
     20        readonly  attribute RTCPeerConnectionState connectionState;
     21                  attribute EventHandler           onconnectionstatechange;
     22      };
     23 
     24    4.4.3.  RTCPeerConnectionState Enum
     25      enum RTCPeerConnectionState {
     26        "new",
     27        "connecting",
     28        "connected",
     29        "disconnected",
     30        "failed",
     31        "closed"
     32      };
     33 
     34    5.5.  RTCDtlsTransport Interface
     35      interface RTCDtlsTransport {
     36        readonly attribute RTCIceTransport       iceTransport;
     37        readonly attribute RTCDtlsTransportState state;
     38        ...
     39      };
     40 
     41      enum RTCDtlsTransportState {
     42        "new",
     43        "connecting",
     44        "connected",
     45        "closed",
     46        "failed"
     47      };
     48 
     49    5.6.  RTCIceTransport Interface
     50      interface RTCIceTransport {
     51        readonly attribute RTCIceTransportState state;
     52        ...
     53      };
     54 
     55      enum RTCIceTransportState {
     56        "new",
     57        "checking",
     58        "connected",
     59        "completed",
     60        "failed",
     61        "disconnected",
     62        "closed"
     63      };
     64   */
     65 
     66  /*
     67    4.4.3.  RTCPeerConnectionState Enum
     68      new
     69        Any of the RTCIceTransports or RTCDtlsTransports are in the new
     70        state and none of the transports are in the connecting, checking,
     71        failed or disconnected state, or all transports are in the closed state.
     72   */
     73  test(t => {
     74    const pc = new RTCPeerConnection();
     75    assert_equals(pc.connectionState, 'new');
     76  }, 'Initial connectionState should be new');
     77 
     78  test(t => {
     79    const pc = new RTCPeerConnection();
     80    pc.close();
     81    assert_equals(pc.connectionState, 'closed');
     82  }, 'Closing the connection should set connectionState to closed');
     83 
     84  /*
     85    4.4.3.  RTCPeerConnectionState Enum
     86      connected
     87        All RTCIceTransports and RTCDtlsTransports are in the connected,
     88        completed or closed state and at least of them is in the connected
     89        or completed state.
     90 
     91    5.5.  RTCDtlsTransportState
     92      connected
     93        DTLS has completed negotiation of a secure connection.
     94 
     95    5.6.  RTCIceTransportState
     96      connected
     97        The RTCIceTransport has found a usable connection, but is still
     98        checking other candidate pairs to see if there is a better connection.
     99        It may also still be gathering and/or waiting for additional remote
    100        candidates. If consent checks [RFC7675] fail on the connection in use,
    101        and there are no other successful candidate pairs available, then the
    102        state transitions to "checking" (if there are candidate pairs remaining
    103        to be checked) or "disconnected" (if there are no candidate pairs to
    104        check, but the peer is still gathering and/or waiting for additional
    105        remote candidates).
    106 
    107      completed
    108        The RTCIceTransport has finished gathering, received an indication that
    109        there are no more remote candidates, finished checking all candidate
    110        pairs and found a connection. If consent checks [RFC7675] subsequently
    111        fail on all successful candidate pairs, the state transitions to "failed".
    112   */
    113 
    114  async_test(t => {
    115    const pc1 = new RTCPeerConnection();
    116    t.add_cleanup(() => pc1.close());
    117    const pc2 = new RTCPeerConnection();
    118    t.add_cleanup(() => pc2.close());
    119 
    120    let had_connecting = false;
    121 
    122    const onConnectionStateChange = t.step_func(() => {
    123      const {connectionState} = pc1;
    124      if (connectionState === 'connecting') {
    125        had_connecting = true;
    126      } else if (connectionState === 'connected') {
    127        assert_true(had_connecting, "state should pass connecting before reaching connected");
    128        t.done();
    129      }
    130    });
    131 
    132    pc1.createDataChannel('test');
    133 
    134    pc1.addEventListener('connectionstatechange', onConnectionStateChange);
    135 
    136    exchangeIceCandidates(pc1, pc2);
    137    exchangeOfferAnswer(pc1, pc2);
    138  }, 'connection with one data channel should eventually have connected connection state');
    139 
    140  async_test(t => {
    141    const pc1 = new RTCPeerConnection();
    142    t.add_cleanup(() => pc1.close());
    143    const pc2 = new RTCPeerConnection();
    144    t.add_cleanup(() => pc2.close());
    145 
    146    const onConnectionStateChange = t.step_func(() => {
    147      const {connectionState} = pc1;
    148      if (connectionState === 'connected') {
    149        const sctpTransport = pc1.sctp;
    150 
    151        const dtlsTransport = sctpTransport.transport;
    152        assert_equals(dtlsTransport.state, 'connected',
    153          'Expect DTLS transport to be in connected state');
    154 
    155        const iceTransport = dtlsTransport.iceTransport
    156        assert_true(iceTransport.state ===  'connected' ||
    157          iceTransport.state === 'completed',
    158          'Expect ICE transport to be in connected or completed state');
    159 
    160        t.done();
    161      }
    162    });
    163 
    164    pc1.createDataChannel('test');
    165 
    166    pc1.addEventListener('connectionstatechange', onConnectionStateChange);
    167 
    168    exchangeIceCandidates(pc1, pc2);
    169    exchangeOfferAnswer(pc1, pc2);
    170  }, 'connection with one data channel should eventually have transports in connected state');
    171 
    172  /*
    173    TODO
    174    4.4.3.  RTCPeerConnectionState Enum
    175      connecting
    176        Any of the RTCIceTransports or RTCDtlsTransports are in the
    177        connecting or checking state and none of them is in the failed state.
    178 
    179      disconnected
    180        Any of the RTCIceTransports or RTCDtlsTransports are in the disconnected
    181        state and none of them are in the failed or connecting or checking state.
    182 
    183      failed
    184        Any of the RTCIceTransports or RTCDtlsTransports are in a failed state.
    185 
    186      closed
    187        The RTCPeerConnection object's [[isClosed]] slot is true.
    188 
    189     5.5. RTCDtlsTransportState
    190      new
    191        DTLS has not started negotiating yet.
    192 
    193      connecting
    194        DTLS is in the process of negotiating a secure connection.
    195 
    196      closed
    197        The transport has been closed.
    198 
    199      failed
    200        The transport has failed as the result of an error (such as a failure
    201        to validate the remote fingerprint).
    202 
    203    5.6.  RTCIceTransportState
    204      new
    205        The RTCIceTransport is gathering candidates and/or waiting for
    206        remote candidates to be supplied, and has not yet started checking.
    207 
    208      checking
    209        The RTCIceTransport has received at least one remote candidate and
    210        is checking candidate pairs and has either not yet found a connection
    211        or consent checks [RFC7675] have failed on all previously successful
    212        candidate pairs. In addition to checking, it may also still be gathering.
    213 
    214      failed
    215        The RTCIceTransport has finished gathering, received an indication that
    216        there are no more remote candidates, finished checking all candidate pairs,
    217        and all pairs have either failed connectivity checks or have lost consent.
    218 
    219      disconnected
    220        The ICE Agent has determined that connectivity is currently lost for this
    221        RTCIceTransport . This is more aggressive than failed, and may trigger
    222        intermittently (and resolve itself without action) on a flaky network.
    223        The way this state is determined is implementation dependent.
    224 
    225        Examples include:
    226          Losing the network interface for the connection in use.
    227          Repeatedly failing to receive a response to STUN requests.
    228 
    229        Alternatively, the RTCIceTransport has finished checking all existing
    230        candidates pairs and failed to find a connection (or consent checks
    231        [RFC7675] once successful, have now failed), but it is still gathering
    232        and/or waiting for additional remote candidates.
    233 
    234      closed
    235        The RTCIceTransport has shut down and is no longer responding to STUN requests.
    236   */
    237  promise_test(async t => {
    238    const caller = new RTCPeerConnection();
    239    t.add_cleanup(() => caller.close());
    240    const callee = new RTCPeerConnection();
    241    t.add_cleanup(() => callee.close());
    242    const stream = await getNoiseStream({audio: true});
    243    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    244    const [track] = stream.getTracks();
    245    caller.addTrack(track, stream);
    246 
    247    await exchangeOfferAnswer(caller, callee);
    248 
    249    assert_equals(caller.iceConnectionState, 'new');
    250    assert_equals(callee.iceConnectionState, 'new');
    251  }, 'connectionState remains new when not adding remote ice candidates');
    252 
    253  promise_test(async t => {
    254 
    255    const caller = new RTCPeerConnection();
    256    t.add_cleanup(() => caller.close());
    257    const callee = new RTCPeerConnection();
    258    t.add_cleanup(() => callee.close());
    259    const stream = await getNoiseStream({audio: true});
    260    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    261    const [track] = stream.getTracks();
    262    caller.addTrack(track, stream);
    263 
    264    const states = [];
    265    caller.addEventListener('connectionstatechange', () => states.push(caller.connectionState));
    266    exchangeIceCandidates(caller, callee);
    267    await exchangeOfferAnswer(caller, callee);
    268 
    269    await listenToConnected(caller);
    270 
    271    assert_array_equals(states, ['connecting', 'connected']);
    272  }, 'connectionState transitions to connected via connecting');
    273 
    274 
    275  // Make the callee act as if not bundle-aware
    276  async function exchangeOfferAnswerUnbundled(caller, callee) {
    277    const offer = await caller.createOffer();
    278    const sdp = offer.sdp.replace('BUNDLE', 'SOMETHING')
    279          .replace(/rtp-hdrext:sdes/g, 'rtp-hdrext:something')
    280          .replace(/a=ssrc:/g, 'a=notssrc');
    281    await caller.setLocalDescription(offer);
    282    await callee.setRemoteDescription({type: 'offer', sdp});
    283 
    284    await exchangeAnswer(caller, callee);
    285  }
    286 
    287  promise_test(async t => {
    288    const pc1 = new RTCPeerConnection({bundlePolicy: 'max-compat'});
    289    t.add_cleanup(() => pc1.close());
    290    const pc2 = new RTCPeerConnection();
    291    t.add_cleanup(() => pc2.close());
    292    const stream = await getNoiseStream({ audio: true });
    293    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    294    stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    295    exchangeIceCandidates(pc1, pc2);
    296    exchangeOfferAnswerUnbundled(pc1, pc2);
    297    await listenToConnected(pc1);
    298 
    299    // https://github.com/w3c/webrtc-pc/issues/2678#issuecomment-948554126
    300    let had_intermediary_connecting = false
    301    let channel;
    302    const onConnectionStateChange = t.step_func(() => {
    303      const {connectionState, iceConnectionState} = pc1;
    304      if (connectionState === 'connecting') {
    305        had_intermediary_connecting = true;
    306      }
    307    });
    308 
    309    pc1.addEventListener('connectionstatechange', onConnectionStateChange);
    310    channel = pc1.createDataChannel('test');
    311    await exchangeOfferAnswer(pc1, pc2);
    312    await listenToConnected(pc1);
    313 
    314    assert_true(had_intermediary_connecting, "state should re-pass connecting before reaching connected");
    315  }, 'when adding a datachannel to an existing unbundled connected PC, it should go through a connecting state');
    316 
    317 
    318  promise_test(async t => {
    319    const pc1 = new RTCPeerConnection();
    320    t.add_cleanup(() => pc1.close());
    321    const pc2 = new RTCPeerConnection();
    322    const stream = await getNoiseStream({ audio: true });
    323    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    324 
    325    stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    326    exchangeIceCandidates(pc1, pc2);
    327    exchangeOfferAnswer(pc1, pc2);
    328    await listenToIceConnected(pc2);
    329 
    330    pc2.onconnectionstatechange = t.unreached_func();
    331    pc2.close();
    332    assert_equals(pc2.connectionState, 'closed');
    333    await new Promise(r => t.step_timeout(r, 100));
    334  }, 'Closing a PeerConnection should not fire connectionstatechange event');
    335 </script>