tor-browser

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

RTCIceTransport-extension.https.html (13896B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCIceTransport-extensions.https.html</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="RTCIceTransport-extension-helper.js"></script>
      7 <script>
      8 'use strict';
      9 
     10 // These tests are based on the following extension specification:
     11 // https://w3c.github.io/webrtc-ice/
     12 
     13 // The following helper functions are called from
     14 // RTCIceTransport-extension-helper.js:
     15 //   makeIceTransport
     16 //   makeGatherAndStartTwoIceTransports
     17 
     18 const ICE_UFRAG = 'u'.repeat(4);
     19 const ICE_PWD = 'p'.repeat(22);
     20 
     21 test(() => {
     22  const iceTransport = new RTCIceTransport();
     23 }, 'RTCIceTransport constructor does not throw');
     24 
     25 test(() => {
     26  const iceTransport = new RTCIceTransport();
     27  assert_equals(iceTransport.role, null, 'Expect role to be null');
     28  assert_equals(iceTransport.state, 'new', `Expect state to be 'new'`);
     29  assert_equals(iceTransport.gatheringState, 'new',
     30    `Expect gatheringState to be 'new'`);
     31  assert_array_equals(iceTransport.getLocalCandidates(), [],
     32    'Expect no local candidates');
     33  assert_array_equals(iceTransport.getRemoteCandidates(), [],
     34    'Expect no remote candidates');
     35  assert_equals(iceTransport.getSelectedCandidatePair(), null,
     36    'Expect no selected candidate pair');
     37  assert_not_equals(iceTransport.getLocalParameters(), null,
     38    'Expect local parameters generated');
     39  assert_equals(iceTransport.getRemoteParameters(), null,
     40    'Expect no remote parameters');
     41 }, 'RTCIceTransport initial properties are set');
     42 
     43 test(t => {
     44  const iceTransport = makeIceTransport(t);
     45  assert_throws_js(TypeError, () =>
     46    iceTransport.gather({ iceServers: null }));
     47 }, 'gather() with { iceServers: null } should throw TypeError');
     48 
     49 test(t => {
     50  const iceTransport = makeIceTransport(t);
     51  iceTransport.gather({ iceServers: undefined });
     52 }, 'gather() with { iceServers: undefined } should succeed');
     53 
     54 test(t => {
     55  const iceTransport = makeIceTransport(t);
     56  iceTransport.gather({ iceServers: [{
     57    urls: ['turns:turn.example.org', 'turn:turn.example.net'],
     58    username: 'user',
     59    credential: 'cred',
     60  }] });
     61 }, 'gather() with one turns server, one turn server, username, credential' +
     62    ' should succeed');
     63 
     64 test(t => {
     65  const iceTransport = makeIceTransport(t);
     66  iceTransport.gather({ iceServers: [{
     67    urls: ['stun:stun1.example.net', 'stun:stun2.example.net'],
     68  }] });
     69 }, 'gather() with 2 stun servers should succeed');
     70 
     71 test(t => {
     72  const iceTransport = makeIceTransport(t);
     73  iceTransport.stop();
     74  assert_throws_dom('InvalidStateError', () => iceTransport.gather({}));
     75 }, 'gather() throws if closed');
     76 
     77 test(t => {
     78  const iceTransport = makeIceTransport(t);
     79  iceTransport.gather({});
     80  assert_equals(iceTransport.gatheringState, 'gathering');
     81 }, `gather() transitions gatheringState to 'gathering'`);
     82 
     83 test(t => {
     84  const iceTransport = makeIceTransport(t);
     85  iceTransport.gather({});
     86  assert_throws_dom('InvalidStateError', () => iceTransport.gather({}));
     87 }, 'gather() throws if called twice');
     88 
     89 promise_test(async t => {
     90  const iceTransport = makeIceTransport(t);
     91  const watcher = new EventWatcher(t, iceTransport, 'gatheringstatechange');
     92  iceTransport.gather({});
     93  await watcher.wait_for('gatheringstatechange');
     94  assert_equals(iceTransport.gatheringState, 'complete');
     95 }, `eventually transition gatheringState to 'complete'`);
     96 
     97 promise_test(async t => {
     98  const iceTransport = makeIceTransport(t);
     99  const watcher = new EventWatcher(t, iceTransport,
    100      [ 'icecandidate', 'gatheringstatechange' ]);
    101  iceTransport.gather({});
    102  let candidate;
    103  do {
    104    (({ candidate } = await watcher.wait_for('icecandidate')));
    105  } while (candidate !== null);
    106  assert_equals(iceTransport.gatheringState, 'gathering');
    107  await watcher.wait_for('gatheringstatechange');
    108  assert_equals(iceTransport.gatheringState, 'complete');
    109 }, 'onicecandidate fires with null candidate before gatheringState' +
    110    ` transitions to 'complete'`);
    111 
    112 promise_test(async t => {
    113  const iceTransport = makeIceTransport(t);
    114  const watcher = new EventWatcher(t, iceTransport, 'icecandidate');
    115  iceTransport.gather({});
    116  const { candidate } = await watcher.wait_for('icecandidate');
    117  assert_not_equals(candidate.candidate, '');
    118  assert_array_equals(iceTransport.getLocalCandidates(), [candidate]);
    119 }, 'gather() returns at least one host candidate');
    120 
    121 promise_test(async t => {
    122  const iceTransport = makeIceTransport(t);
    123  const watcher = new EventWatcher(t, iceTransport, 'icecandidate');
    124  iceTransport.gather({ gatherPolicy: 'relay' });
    125  const { candidate } = await watcher.wait_for('icecandidate');
    126  assert_equals(candidate, null);
    127  assert_array_equals(iceTransport.getLocalCandidates(), []);
    128 }, `gather() returns no candidates with { gatherPolicy: 'relay'} and no turn` +
    129    ' servers');
    130 
    131 const dummyRemoteParameters = {
    132  usernameFragment: ICE_UFRAG,
    133  password: ICE_PWD,
    134 };
    135 
    136 test(() => {
    137  const iceTransport = new RTCIceTransport();
    138  iceTransport.stop();
    139  assert_throws_dom('InvalidStateError',
    140    () => iceTransport.start(dummyRemoteParameters));
    141  assert_equals(iceTransport.getRemoteParameters(), null);
    142 }, `start() throws if closed`);
    143 
    144 test(() => {
    145  const iceTransport = new RTCIceTransport();
    146  assert_throws_js(TypeError, () => iceTransport.start({}));
    147  assert_throws_js(TypeError,
    148    () => iceTransport.start({ usernameFragment: ICE_UFRAG }));
    149  assert_throws_js(TypeError,
    150    () => iceTransport.start({ password: ICE_PWD }));
    151  assert_equals(iceTransport.getRemoteParameters(), null);
    152 }, 'start() throws if usernameFragment or password not set');
    153 
    154 test(() => {
    155  const TEST_CASES = [
    156    {usernameFragment: '2sh', description: 'less than 4 characters long'},
    157    {
    158      usernameFragment: 'x'.repeat(257),
    159      description: 'greater than 256 characters long',
    160    },
    161    {usernameFragment: '123\n', description: 'illegal character'},
    162  ];
    163  for (const {usernameFragment, description} of TEST_CASES) {
    164    const iceTransport = new RTCIceTransport();
    165    assert_throws_dom(
    166      'SyntaxError',
    167      () => iceTransport.start({ usernameFragment, password: ICE_PWD }),
    168      `illegal usernameFragment (${description}) should throw a SyntaxError`);
    169  }
    170 }, 'start() throws if usernameFragment does not conform to syntax');
    171 
    172 test(() => {
    173  const TEST_CASES = [
    174    {password: 'x'.repeat(21), description: 'less than 22 characters long'},
    175    {
    176      password: 'x'.repeat(257),
    177      description: 'greater than 256 characters long',
    178    },
    179    {password: ('x'.repeat(21) + '\n'), description: 'illegal character'},
    180  ];
    181  for (const {password, description} of TEST_CASES) {
    182    const iceTransport = new RTCIceTransport();
    183    assert_throws_dom(
    184      'SyntaxError',
    185      () => iceTransport.start({ usernameFragment: ICE_UFRAG, password }),
    186      `illegal password (${description}) should throw a SyntaxError`);
    187  }
    188 }, 'start() throws if password does not conform to syntax');
    189 
    190 const assert_ice_parameters_equals = (a, b) => {
    191  assert_equals(a.usernameFragment, b.usernameFragment,
    192      'usernameFragments are equal');
    193  assert_equals(a.password, b.password, 'passwords are equal');
    194 };
    195 
    196 test(t => {
    197  const iceTransport = makeIceTransport(t);
    198  iceTransport.start(dummyRemoteParameters);
    199  assert_equals(iceTransport.state, 'new');
    200  assert_ice_parameters_equals(iceTransport.getRemoteParameters(),
    201      dummyRemoteParameters);
    202 }, `start() does not transition state to 'checking' if no remote candidates ` +
    203    'added');
    204 
    205 test(t => {
    206  const iceTransport = makeIceTransport(t);
    207  iceTransport.start(dummyRemoteParameters);
    208  assert_equals(iceTransport.role, 'controlled');
    209 }, `start() with default role sets role attribute to 'controlled'`);
    210 
    211 test(t => {
    212  const iceTransport = makeIceTransport(t);
    213  iceTransport.start(dummyRemoteParameters, 'controlling');
    214  assert_equals(iceTransport.role, 'controlling');
    215 }, `start() sets role attribute to 'controlling'`);
    216 
    217 const candidate1 = new RTCIceCandidate({
    218  candidate: 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host',
    219  sdpMid: '',
    220 });
    221 
    222 test(() => {
    223  const iceTransport = new RTCIceTransport();
    224  iceTransport.stop();
    225  assert_throws_dom('InvalidStateError',
    226    () => iceTransport.addRemoteCandidate(candidate1));
    227  assert_array_equals(iceTransport.getRemoteCandidates(), []);
    228 }, 'addRemoteCandidate() throws if closed');
    229 
    230 test(() => {
    231  const iceTransport = new RTCIceTransport();
    232  assert_throws_dom('OperationError',
    233    () => iceTransport.addRemoteCandidate(
    234      new RTCIceCandidate({ candidate: 'invalid', sdpMid: '' })));
    235  assert_array_equals(iceTransport.getRemoteCandidates(), []);
    236 }, 'addRemoteCandidate() throws on invalid candidate');
    237 
    238 test(t => {
    239  const iceTransport = makeIceTransport(t);
    240  iceTransport.addRemoteCandidate(candidate1);
    241  iceTransport.start(dummyRemoteParameters);
    242  assert_equals(iceTransport.state, 'checking');
    243  assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]);
    244 }, `start() transitions state to 'checking' if one remote candidate had been ` +
    245    'added');
    246 
    247 test(t => {
    248  const iceTransport = makeIceTransport(t);
    249  iceTransport.start(dummyRemoteParameters);
    250  iceTransport.addRemoteCandidate(candidate1);
    251  assert_equals(iceTransport.state, 'checking');
    252  assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]);
    253 }, `addRemoteCandidate() transitions state to 'checking' if start() had been ` +
    254    'called before');
    255 
    256 test(t => {
    257  const iceTransport = makeIceTransport(t);
    258  iceTransport.start(dummyRemoteParameters);
    259  assert_throws_dom('InvalidStateError',
    260    () => iceTransport.start(dummyRemoteParameters, 'controlling'));
    261 }, 'start() throws if later called with a different role');
    262 
    263 test(t => {
    264  const iceTransport = makeIceTransport(t);
    265  iceTransport.start({
    266    usernameFragment: '1'.repeat(4),
    267    password: '1'.repeat(22),
    268  });
    269  iceTransport.addRemoteCandidate(candidate1);
    270  const changedRemoteParameters = {
    271    usernameFragment: '2'.repeat(4),
    272    password: '2'.repeat(22),
    273  };
    274  iceTransport.start(changedRemoteParameters);
    275  assert_equals(iceTransport.state, 'new');
    276  assert_array_equals(iceTransport.getRemoteCandidates(), []);
    277  assert_ice_parameters_equals(iceTransport.getRemoteParameters(),
    278      changedRemoteParameters);
    279 }, `start() flushes remote candidates and transitions state to 'new' if ` +
    280   'later called with different remote parameters');
    281 
    282 promise_test(async t => {
    283  const [ localTransport, remoteTransport ] =
    284      makeGatherAndStartTwoIceTransports(t);
    285  const localWatcher = new EventWatcher(t, localTransport, 'statechange');
    286  const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange');
    287  await Promise.all([
    288    localWatcher.wait_for('statechange').then(() => {
    289      assert_equals(localTransport.state, 'connected');
    290    }),
    291    remoteWatcher.wait_for('statechange').then(() => {
    292      assert_equals(remoteTransport.state, 'connected');
    293    }),
    294  ]);
    295 }, 'Two RTCIceTransports connect to each other');
    296 
    297 ['controlling', 'controlled'].forEach(role => {
    298  promise_test(async t => {
    299    const [ localTransport, remoteTransport ] =
    300        makeAndGatherTwoIceTransports(t);
    301    localTransport.start(remoteTransport.getLocalParameters(), role);
    302    remoteTransport.start(localTransport.getLocalParameters(), role);
    303    const localWatcher = new EventWatcher(t, localTransport, 'statechange');
    304    const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange');
    305    await Promise.all([
    306      localWatcher.wait_for('statechange').then(() => {
    307        assert_equals(localTransport.state, 'connected');
    308      }),
    309      remoteWatcher.wait_for('statechange').then(() => {
    310        assert_equals(remoteTransport.state, 'connected');
    311      }),
    312    ]);
    313  }, `Two RTCIceTransports configured with the ${role} role resolve the ` +
    314      'conflict in band and still connect.');
    315 });
    316 
    317 promise_test(async t => {
    318  async function waitForSelectedCandidatePairChangeThenConnected(t, transport,
    319      transportName) {
    320    const watcher = new EventWatcher(t, transport,
    321        [ 'statechange', 'selectedcandidatepairchange' ]);
    322    await watcher.wait_for('selectedcandidatepairchange');
    323    const selectedCandidatePair = transport.getSelectedCandidatePair();
    324    assert_not_equals(selectedCandidatePair, null,
    325        `${transportName} selected candidate pair should not be null once ` +
    326        'the selectedcandidatepairchange event fires');
    327    assert_true(
    328        transport.getLocalCandidates().some(
    329            ({ candidate }) =>
    330                candidate === selectedCandidatePair.local.candidate),
    331        `${transportName} selected candidate pair local should be in the ` +
    332        'list of local candidates');
    333    assert_true(
    334        transport.getRemoteCandidates().some(
    335            ({ candidate }) =>
    336                candidate === selectedCandidatePair.remote.candidate),
    337        `${transportName} selected candidate pair local should be in the ` +
    338        'list of remote candidates');
    339    await watcher.wait_for('statechange');
    340    assert_equals(transport.state, 'connected',
    341        `${transportName} state should be 'connected'`);
    342  }
    343  const [ localTransport, remoteTransport ] =
    344      makeGatherAndStartTwoIceTransports(t);
    345  await Promise.all([
    346    waitForSelectedCandidatePairChangeThenConnected(t, localTransport,
    347        'local transport'),
    348    waitForSelectedCandidatePairChangeThenConnected(t, remoteTransport,
    349        'remote transport'),
    350  ]);
    351 }, 'Selected candidate pair changes once the RTCIceTransports connect.');
    352 
    353 promise_test(async t => {
    354  const [ transport, ] = makeGatherAndStartTwoIceTransports(t);
    355  const watcher = new EventWatcher(t, transport, 'selectedcandidatepairchange');
    356  await watcher.wait_for('selectedcandidatepairchange');
    357  transport.stop();
    358  assert_equals(transport.getSelectedCandidatePair(), null);
    359 }, 'getSelectedCandidatePair() returns null once the RTCIceTransport is ' +
    360    'stopped.');
    361 
    362 </script>