tor-browser

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

RTCPeerConnection-iceConnectionState.https.html (16487B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCPeerConnection.prototype.iceConnectionState</title>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="RTCPeerConnection-helper.js"></script>
      8 <script>
      9  'use strict';
     10 
     11  // Test is based on the following editor draft:
     12  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     13 
     14  /*
     15    4.3.2.  Interface Definition
     16      interface RTCPeerConnection : EventTarget {
     17        ...
     18        readonly  attribute RTCIceConnectionState  iceConnectionState;
     19                  attribute EventHandler           oniceconnectionstatechange;
     20      };
     21 
     22    4.4.4 RTCIceConnectionState Enum
     23      enum RTCIceConnectionState {
     24        "new",
     25        "checking",
     26        "connected",
     27        "completed",
     28        "failed",
     29        "disconnected",
     30        "closed"
     31      };
     32 
     33    5.6.  RTCIceTransport Interface
     34      interface RTCIceTransport {
     35        readonly attribute RTCIceTransportState state;
     36                 attribute EventHandler         onstatechange;
     37 
     38        ...
     39      };
     40 
     41      enum RTCIceTransportState {
     42        "new",
     43        "checking",
     44        "connected",
     45        "completed",
     46        "failed",
     47        "disconnected",
     48        "closed"
     49      };
     50   */
     51 
     52  /*
     53    4.4.4 RTCIceConnectionState Enum
     54      new
     55        Any of the RTCIceTransports are in the new state and none of them
     56        are in the checking, failed or disconnected state, or all
     57        RTCIceTransport s are in the closed state.
     58   */
     59  test(t => {
     60    const pc = new RTCPeerConnection();
     61    assert_equals(pc.iceConnectionState, 'new');
     62  }, 'Initial iceConnectionState should be new');
     63 
     64  test(t => {
     65    const pc = new RTCPeerConnection();
     66    pc.close();
     67    assert_equals(pc.iceConnectionState, 'closed');
     68  }, 'Closing the connection should set iceConnectionState to closed');
     69 
     70  /*
     71    4.4.4 RTCIceConnectionState Enum
     72      checking
     73        Any of the RTCIceTransport s are in the checking state and none of
     74        them are in the failed or disconnected state.
     75 
     76      connected
     77        All RTCIceTransport s are in the connected, completed or closed state
     78        and at least one of them is in the connected state.
     79 
     80      completed
     81        All RTCIceTransport s are in the completed or closed state and at least
     82        one of them is in the completed state.
     83 
     84      checking
     85        The RTCIceTransport has received at least one remote candidate and
     86        is checking candidate pairs and has either not yet found a connection
     87        or consent checks [RFC7675] have failed on all previously successful
     88        candidate pairs. In addition to checking, it may also still be gathering.
     89 
     90    5.6.  enum RTCIceTransportState
     91      connected
     92        The RTCIceTransport has found a usable connection, but is still
     93        checking other candidate pairs to see if there is a better connection.
     94        It may also still be gathering and/or waiting for additional remote
     95        candidates. If consent checks [RFC7675] fail on the connection in use,
     96        and there are no other successful candidate pairs available, then the
     97        state transitions to "checking" (if there are candidate pairs remaining
     98        to be checked) or "disconnected" (if there are no candidate pairs to
     99        check, but the peer is still gathering and/or waiting for additional
    100        remote candidates).
    101 
    102      completed
    103        The RTCIceTransport has finished gathering, received an indication that
    104        there are no more remote candidates, finished checking all candidate
    105        pairs and found a connection. If consent checks [RFC7675] subsequently
    106        fail on all successful candidate pairs, the state transitions to "failed".
    107   */
    108  async_test(t => {
    109    const pc1 = new RTCPeerConnection();
    110    t.add_cleanup(() => pc1.close());
    111    const pc2 = new RTCPeerConnection();
    112    t.add_cleanup(() => pc2.close());
    113 
    114    let had_checking = false;
    115 
    116    const onIceConnectionStateChange = t.step_func(() => {
    117      const {iceConnectionState} = pc1;
    118      if (iceConnectionState === 'checking') {
    119        had_checking = true;
    120      } else if (iceConnectionState === 'connected' ||
    121                 iceConnectionState === 'completed') {
    122        assert_true(had_checking, 'state should pass checking before' +
    123                                  ' reaching connected or completed');
    124        t.done();
    125      } else if (iceConnectionState === 'failed') {
    126        assert_unreached("ICE should not fail");
    127      }
    128    });
    129 
    130    pc1.createDataChannel('test');
    131 
    132    pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
    133 
    134    exchangeIceCandidates(pc1, pc2);
    135    exchangeOfferAnswer(pc1, pc2);
    136  }, 'connection with one data channel should eventually have connected or ' +
    137     'completed connection state');
    138 
    139 async_test(t => {
    140    const pc1 = new RTCPeerConnection();
    141    t.add_cleanup(() => pc1.close());
    142    const pc2 = new RTCPeerConnection();
    143 
    144    t.add_cleanup(() => pc2.close());
    145 
    146    const onIceConnectionStateChange = t.step_func(() => {
    147      const { iceConnectionState } = pc1;
    148 
    149      if(iceConnectionState === 'checking') {
    150        const iceTransport = pc1.sctp.transport.iceTransport;
    151 
    152        assert_equals(iceTransport.state, 'checking',
    153          'Expect ICE transport to be in checking state when' +
    154          ' iceConnectionState is checking');
    155 
    156      } else if(iceConnectionState === 'connected') {
    157        const iceTransport = pc1.sctp.transport.iceTransport;
    158 
    159        assert_equals(iceTransport.state, 'connected',
    160          'Expect ICE transport to be in connected state when' +
    161          ' iceConnectionState is connected');
    162        t.done();
    163      } else if(iceConnectionState === 'completed') {
    164        const iceTransport = pc1.sctp.transport.iceTransport;
    165 
    166        assert_equals(iceTransport.state, 'completed',
    167          'Expect ICE transport to be in connected state when' +
    168          ' iceConnectionState is completed');
    169        t.done();
    170      } else if (iceConnectionState === 'failed') {
    171        assert_unreached("ICE should not fail");
    172      }
    173    });
    174 
    175    pc1.createDataChannel('test');
    176 
    177    assert_equals(pc1.oniceconnectionstatechange, null,
    178      'Expect connection to have iceconnectionstatechange event');
    179 
    180    pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
    181 
    182    exchangeIceCandidates(pc1, pc2);
    183    exchangeOfferAnswer(pc1, pc2);
    184  }, 'connection with one data channel should eventually ' +
    185     'have connected connection state');
    186 
    187  promise_test(async t => {
    188    const pc1 = new RTCPeerConnection();
    189    t.add_cleanup(() => pc1.close());
    190    const pc2 = new RTCPeerConnection();
    191    t.add_cleanup(() => pc2.close());
    192 
    193    const stream = await getNoiseStream({audio: true});
    194    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    195    stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    196 
    197    exchangeIceCandidates(pc1, pc2);
    198    exchangeOfferAnswer(pc1, pc2);
    199    await listenToIceConnected(pc1);
    200  }, 'connection with audio track should eventually ' +
    201     'have connected connection state');
    202 
    203  promise_test(async t => {
    204    const pc1 = new RTCPeerConnection();
    205    t.add_cleanup(() => pc1.close());
    206    const pc2 = new RTCPeerConnection();
    207    t.add_cleanup(() => pc2.close());
    208 
    209    const stream = await getNoiseStream({audio: true, video:true});
    210    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    211    stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    212 
    213    exchangeIceCandidates(pc1, pc2);
    214    exchangeOfferAnswer(pc1, pc2);
    215    await listenToIceConnected(pc1);
    216  }, 'connection with audio and video tracks should eventually ' +
    217     'have connected connection state');
    218 
    219  promise_test(async t => {
    220    const caller = new RTCPeerConnection();
    221    t.add_cleanup(() => caller.close());
    222    const callee = new RTCPeerConnection();
    223    t.add_cleanup(() => callee.close());
    224 
    225    caller.addTransceiver('audio', {direction:'recvonly'});
    226    const stream = await getNoiseStream({audio:true});
    227    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    228    const [track] = stream.getTracks();
    229    callee.addTrack(track, stream);
    230    exchangeIceCandidates(caller, callee);
    231    await exchangeOfferAnswer(caller, callee);
    232 
    233    assert_equals(caller.getTransceivers().length, 1);
    234    const [transceiver] = caller.getTransceivers();
    235    assert_equals(transceiver.currentDirection, 'recvonly');
    236 
    237    await listenToIceConnected(caller);
    238  }, 'ICE can connect in a recvonly usecase');
    239 
    240  /*
    241    TODO
    242    4.4.4 RTCIceConnectionState Enum
    243      failed
    244        Any of the RTCIceTransport s are in the failed state.
    245 
    246      disconnected
    247        Any of the RTCIceTransport s are in the disconnected state and none of
    248        them are in the failed state.
    249 
    250      closed
    251        The RTCPeerConnection object's [[ isClosed]] slot is true.
    252 
    253    5.6.  enum RTCIceTransportState
    254      new
    255        The RTCIceTransport is gathering candidates and/or waiting for
    256        remote candidates to be supplied, and has not yet started checking.
    257 
    258      failed
    259        The RTCIceTransport has finished gathering, received an indication that
    260        there are no more remote candidates, finished checking all candidate pairs,
    261        and all pairs have either failed connectivity checks or have lost consent.
    262 
    263      disconnected
    264        The ICE Agent has determined that connectivity is currently lost for this
    265        RTCIceTransport . This is more aggressive than failed, and may trigger
    266        intermittently (and resolve itself without action) on a flaky network.
    267        The way this state is determined is implementation dependent.
    268 
    269        Examples include:
    270          Losing the network interface for the connection in use.
    271          Repeatedly failing to receive a response to STUN requests.
    272 
    273        Alternatively, the RTCIceTransport has finished checking all existing
    274        candidates pairs and failed to find a connection (or consent checks
    275        [RFC7675] once successful, have now failed), but it is still gathering
    276        and/or waiting for additional remote candidates.
    277 
    278      closed
    279        The RTCIceTransport has shut down and is no longer responding to STUN requests.
    280  */
    281 
    282 for (let bundle_policy of ['balanced', 'max-bundle', 'max-compat']) {
    283  promise_test(async t => {
    284    const caller = new RTCPeerConnection({bundlePolicy: bundle_policy});
    285    t.add_cleanup(() => caller.close());
    286    const stream = await getNoiseStream(
    287        {audio: true, video:true});
    288    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    289    const [track1, track2] = stream.getTracks();
    290    const sender1 = caller.addTrack(track1);
    291    const sender2 = caller.addTrack(track2);
    292    caller.createDataChannel('datachannel');
    293    const callee = new RTCPeerConnection();
    294    t.add_cleanup(() => callee.close());
    295    exchangeIceCandidates(caller, callee);
    296    const offer = await caller.createOffer();
    297    await caller.setLocalDescription(offer);
    298    const [caller_transceiver1, caller_transceiver2] = caller.getTransceivers();
    299    assert_equals(sender1.transport, caller_transceiver1.sender.transport);
    300    await callee.setRemoteDescription(offer);
    301    const [callee_transceiver1, callee_transceiver2] = callee.getTransceivers();
    302    const answer = await callee.createAnswer();
    303    await callee.setLocalDescription(answer);
    304    await caller.setRemoteDescription(answer);
    305    // At this point, we should have a single ICE transport, and it
    306    // should eventually get to the "connected" state.
    307    await waitForState(caller_transceiver1.receiver.transport.iceTransport,
    308                        'connected');
    309    // The PeerConnection's iceConnectionState should therefore be 'connected'
    310    assert_equals(caller.iceConnectionState, 'connected',
    311                  'PC.iceConnectionState:');
    312  }, 'iceConnectionState changes at the right time, with bundle policy ' +
    313                bundle_policy);
    314 }
    315 
    316 promise_test(async t => {
    317  const pc1 = new RTCPeerConnection();
    318  t.add_cleanup(() => pc1.close());
    319  const pc2 = new RTCPeerConnection();
    320  t.add_cleanup(() => pc2.close());
    321  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    322  pc1.candidateBuffer = [];
    323  pc2.onicecandidate = e => {
    324    // Don't add candidate if candidate buffer is already used
    325    if (pc1.candidateBuffer) {
    326      pc1.candidateBuffer.push(e.candidate)
    327    }
    328  };
    329  pc1.iceStates = [pc1.iceConnectionState];
    330  pc2.iceStates = [pc2.iceConnectionState];
    331  pc1.oniceconnectionstatechange = () => {
    332    pc1.iceStates.push(pc1.iceConnectionState);
    333  };
    334  pc2.oniceconnectionstatechange = () => {
    335    pc2.iceStates.push(pc2.iceConnectionState);
    336  };
    337 
    338  const localStream = await getNoiseStream({audio: true, video: true});
    339  const localStream2 = await getNoiseStream({audio: true, video: true});
    340  const remoteStream = await getNoiseStream({audio: true, video: true});
    341  for (const stream of [localStream, localStream2, remoteStream]) {
    342    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    343  }
    344  localStream.getTracks().forEach(t => pc1.addTrack(t, localStream));
    345  localStream2.getTracks().forEach(t => pc1.addTrack(t, localStream2));
    346  remoteStream.getTracks().forEach(t => pc2.addTrack(t, remoteStream));
    347  const offer = await pc1.createOffer();
    348  await pc2.setRemoteDescription(offer);
    349  await pc1.setLocalDescription(offer);
    350  const answer = await pc2.createAnswer();
    351  await pc2.setLocalDescription(answer);
    352  await pc1.setRemoteDescription(answer);
    353  pc1.candidateBuffer.forEach(c => pc1.addIceCandidate(c));
    354  delete pc1.candidateBuffer;
    355  await listenToIceConnected(pc1);
    356  await listenToIceConnected(pc2);
    357  // While we're waiting for pc2, pc1 may or may not have transitioned
    358  // to "completed" state, so allow for both cases.
    359  if (pc1.iceStates.length == 3) {
    360    assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected']);
    361  } else {
    362    assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected',
    363                                        'completed']);
    364  }
    365  assert_array_equals(pc2.iceStates, ['new', 'checking', 'connected']);
    366 }, 'Responder ICE connection state behaves as expected');
    367 
    368 /*
    369  Test case for step 11 of PeerConnection.close().
    370  ...
    371  11. Set connection's ICE connection state to "closed". This does not invoke
    372      the "update the ICE connection state" procedure, and does not fire any
    373      event.
    374  ...
    375 */
    376 promise_test(async t => {
    377  const pc1 = new RTCPeerConnection();
    378  t.add_cleanup(() => pc1.close());
    379  const pc2 = new RTCPeerConnection();
    380  const stream = await getNoiseStream({ audio: true });
    381  t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    382 
    383  stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    384  exchangeIceCandidates(pc1, pc2);
    385  exchangeOfferAnswer(pc1, pc2);
    386  await listenToIceConnected(pc2);
    387 
    388  pc2.oniceconnectionstatechange = t.unreached_func();
    389  pc2.close();
    390  assert_equals(pc2.iceConnectionState, 'closed');
    391  await new Promise(r => t.step_timeout(r, 100));
    392 }, 'Closing a PeerConnection should not fire iceconnectionstatechange event');
    393 
    394 promise_test(async t => {
    395  const pc1 = new RTCPeerConnection();
    396  t.add_cleanup(() => pc1.close());
    397  const pc2 = new RTCPeerConnection();
    398  const stream = await getNoiseStream({ audio: true });
    399  t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    400 
    401  stream.getTracks().forEach(track => pc1.addTrack(track, stream));
    402  // Only signal candidate from 1->2.
    403  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    404  pc1.iceStates = [pc1.iceConnectionState];
    405  pc1.oniceconnectionstatechange = () => {
    406    pc1.iceStates.push(pc1.iceConnectionState);
    407  };
    408  exchangeOfferAnswer(pc1, pc2);
    409  await listenToIceConnected(pc2);
    410 
    411  assert_true(pc1.iceStates.length >= 2);
    412  assert_equals(pc1.iceStates[1], 'checking');
    413 }, 'iceConnectionState can go to checking without explictly calling addIceCandidate');
    414 
    415 promise_test(async t => {
    416  const pc1 = new RTCPeerConnection();
    417  const pc2 = new RTCPeerConnection();
    418  exchangeIceCandidates(pc1, pc2);
    419  const transceiver = pc1.addTransceiver('audio', { direction: 'recvonly' });
    420  await exchangeOfferAnswer(pc1, pc2);
    421  await listenToIceConnected(pc1);
    422  expectNoMoreIceConnectionStateChanges(t, pc1);
    423  pc1.restartIce();
    424  await exchangeOfferAnswer(pc1, pc2);
    425  await new Promise(r => t.step_timeout(r, 1000));
    426 }, 'ICE restart does not result in a transition back to checking');
    427 
    428 </script>