tor-browser

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

RTCDataChannel-id.html (14179B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCDataChannel id attribute</title>
      5 <script src=/resources/testharness.js></script>
      6 <script src=/resources/testharnessreport.js></script>
      7 <script src="RTCPeerConnection-helper.js"></script>
      8 <script src="RTCDataChannel-helper.js"></script>
      9 <script>
     10 'use strict';
     11 
     12 // Test is based on the following revision:
     13 // https://rawgit.com/w3c/webrtc-pc/1cc5bfc3ff18741033d804c4a71f7891242fb5b3/webrtc.html
     14 
     15 // This is the maximum number of streams, NOT the maximum stream ID (which is 65534)
     16 // See: https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.2
     17 const nStreams = 65535;
     18 
     19 /*
     20  The ID is assigned after SCTP connects, according to section 6.1.1.3
     21  (RTCSctpTransport Connected procedure)
     22 */
     23 promise_test(async (t) => {
     24  const pc1 = new RTCPeerConnection;
     25  t.add_cleanup(() => pc1.close());
     26  const pc2 = new RTCPeerConnection;
     27  t.add_cleanup(() => pc2.close());
     28  exchangeIceCandidates(pc1, pc2);
     29 
     30  const dc1 = pc1.createDataChannel('client_ids');
     31  const ids = new UniqueSet();
     32 
     33  const initial_offer = await pc1.createOffer();
     34  // Ensure that pc1 is "passive" and pc2 is "active"
     35  const offer = {
     36    type: 'offer',
     37    sdp: initial_offer.sdp.replace('actpass', 'passive'),
     38  }
     39  await Promise.all([pc1.setLocalDescription(initial_offer),
     40                     pc2.setRemoteDescription(offer)]);
     41  const answer = await pc2.createAnswer();
     42  await Promise.all([pc1.setRemoteDescription(answer),
     43                     pc2.setLocalDescription(answer)]);
     44  await waitForState(pc1.sctp, 'connected');
     45 
     46  // Since the remote description had an 'active' DTLS role, we're the server
     47  // and should use odd data channel IDs, according to rtcweb-data-channel.
     48  assert_equals(dc1.id % 2, 1,
     49    `Channel created by the DTLS server role must be odd (was ${dc1.id})`);
     50  const dc2 = pc1.createDataChannel('another');
     51  assert_equals(dc2.id % 2, 1,
     52    `Channel created by the DTLS server role must be odd (was ${dc2.id})`);
     53 
     54  // Ensure IDs are unique
     55  ids.add(dc1.id, `Channel ID ${dc1.id} should be unique`);
     56  ids.add(dc2.id, `Channel ID ${dc2.id} should be unique`);
     57 }, 'DTLS client uses odd data channel IDs');
     58 
     59 promise_test(async (t) => {
     60  const pc = new RTCPeerConnection;
     61  t.add_cleanup(() => pc.close());
     62 
     63  const dc1 = pc.createDataChannel('server_ids');
     64  const ids = new UniqueSet();
     65 
     66  const offer = await pc.createOffer();
     67  await pc.setLocalDescription(offer);
     68  // Turn our own offer SDP into valid answer SDP by setting the DTLS role to
     69  // 'passive'.
     70  const answer = {
     71    type: 'answer',
     72    sdp: pc.localDescription.sdp.replace('actpass', 'passive')
     73  };
     74  await pc.setRemoteDescription(answer);
     75 
     76  // Since the remote description had a 'passive' DTLS role, we're the client
     77  // and should use even data channel IDs, according to rtcweb-data-channel.
     78  assert_equals(dc1.id % 2, 0,
     79    `Channel created by the DTLS client role must be even (was ${dc1.id})`);
     80  const dc2 = pc.createDataChannel('another');
     81  assert_equals(dc2.id % 2, 0,
     82    `Channel created by the DTLS client role must be even (was ${dc1.id})`);
     83 
     84  // Ensure IDs are unique
     85  ids.add(dc1.id, `Channel ID ${dc1.id} should be unique`);
     86  ids.add(dc2.id, `Channel ID ${dc2.id} should be unique`);
     87 }, 'DTLS server uses even data channel IDs');
     88 
     89 /*
     90  Checks that the id is ignored if "negotiated" is false.
     91  See section 6.1, createDataChannel step 13.
     92 */
     93 promise_test(async (t) => {
     94  const pc1 = new RTCPeerConnection();
     95  const pc2 = new RTCPeerConnection();
     96  t.add_cleanup(() => pc1.close());
     97  t.add_cleanup(() => pc2.close());
     98 
     99  const dc1 = pc1.createDataChannel('ignore_id_prop', {
    100    negotiated: false,
    101    id: 42
    102  });
    103  dc1.onopen = t.step_func(() => {
    104    dc1.send(':(');
    105  });
    106 
    107  const dc2 = pc2.createDataChannel('ignore_id_prop', {
    108    negotiated: false,
    109    id: 42
    110  });
    111  // ID should be null prior to negotiation.
    112  assert_equals(dc1.id, null);
    113  assert_equals(dc2.id, null);
    114 
    115  exchangeIceCandidates(pc1, pc2);
    116  await exchangeOfferAnswer(pc1, pc2);
    117  // We should now have 2 datachannels with different IDs.
    118  // At least one of the datachannels should not be 42.
    119  // If one has the value 42, it's an accident; if both have,
    120  // they are the same datachannel, and it's a bug.
    121  assert_false(dc1.id == 42 && dc2.id == 42);
    122 }, 'In-band negotiation with a specific ID should not work');
    123 
    124 /*
    125  Check if the implementation still follows the odd/even role correctly if we annoy it with
    126  negotiated channels not following that rule.
    127 
    128  Note: This test assumes that the implementation can handle a minimum of 40 data channels.
    129 */
    130 promise_test(async (t) => {
    131  // Takes the DTLS server role
    132  const pc1 = new RTCPeerConnection();
    133  // Takes the DTLS client role
    134  const pc2 = new RTCPeerConnection();
    135  t.add_cleanup(() => pc1.close());
    136  t.add_cleanup(() => pc2.close());
    137 
    138  exchangeIceCandidates(pc1, pc2);
    139  const dcs = [];
    140  const negotiatedDcs = [];
    141  const ids = new UniqueSet();
    142 
    143  // Create 10 DCEP-negotiated channels with pc1
    144  // Note: These should not have any associated valid ID at this point
    145  for (let i = 0; i < 10; ++i) {
    146    const dc = pc1.createDataChannel(`before-connection-${i}`);
    147    assert_equals(dc.id, null, 'Channel id must be null before DTLS role has been determined');
    148    dcs.push(dc);
    149  }
    150 
    151  // Create 10 negotiated channels with pc1 violating the odd/even rule
    152  for (let id = 0; id < 20; id += 2) {
    153    const dc = pc1.createDataChannel(`negotiated-not-odd-${id}-before-connection`, {
    154      negotiated: true,
    155      id: id,
    156    });
    157    assert_equals(dc.id, id, 'Channel id must be set before DTLS role has been determined when negotiated is true');
    158    negotiatedDcs.push([dc, id]);
    159    ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
    160  }
    161 
    162  await exchangeOfferAnswer(pc1, pc2, {
    163    offer: (offer) => {
    164      // Ensure pc1 takes the server role
    165      assert_true(offer.sdp.includes('actpass') || offer.sdp.includes('passive'),
    166        'pc1 must take the DTLS server role');
    167      return offer;
    168    },
    169    answer: (answer) => {
    170      // Ensure pc2 takes the client role
    171      // Note: It very likely will choose 'active' itself
    172      answer.sdp = answer.sdp.replace('actpass', 'active');
    173      assert_true(answer.sdp.includes('active'), 'pc2 must take the DTLS client role');
    174      return answer;
    175    },
    176  });
    177  await waitForState(pc1.sctp, 'connected');
    178  for (const dc of dcs) {
    179    assert_equals(dc.id % 2, 1,
    180      `Channel created by the DTLS server role must be odd (was ${dc.id})`);
    181    ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
    182  }
    183 
    184  // Create 10 channels with pc1
    185  for (let i = 0; i < 10; ++i) {
    186    const dc = pc1.createDataChannel(`after-connection-${i}`);
    187    assert_equals(dc.id % 2, 1,
    188      `Channel created by the DTLS server role must be odd (was ${dc.id})`);
    189    dcs.push(dc);
    190    ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
    191  }
    192 
    193  // Create 10 negotiated channels with pc1 violating the odd/even rule
    194  for (let i = 0; i < 10; ++i) {
    195    // Generate a valid even ID that has not been taken, yet.
    196    let id = 20;
    197    while (ids.has(id)) {
    198      id += 2;
    199    }
    200    const dc = pc1.createDataChannel(`negotiated-not-odd-${i}-after-connection`, {
    201      negotiated: true,
    202      id: id,
    203    });
    204    negotiatedDcs.push([dc, id]);
    205    ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
    206  }
    207 
    208  // Since we've added new channels, let's check again that the odd/even role is not violated
    209  for (const dc of dcs) {
    210    assert_equals(dc.id % 2, 1,
    211      `Channel created by the DTLS server role must be odd (was ${dc.id})`);
    212  }
    213 
    214  // Let's also make sure the negotiated channels have kept their ID
    215  for (const [dc, id] of negotiatedDcs) {
    216    assert_equals(dc.id, id, 'Negotiated channels should keep their assigned ID');
    217  }
    218 }, 'Odd/even role should not be violated when mixing with negotiated channels');
    219 
    220 promise_test(async t => {
    221  const offerer = new RTCPeerConnection();
    222  const answerer = new RTCPeerConnection();
    223  t.add_cleanup(() => offerer.close());
    224  t.add_cleanup(() => answerer.close());
    225 
    226  const pairPromise = openChannelPair(offerer, answerer, 'foo');
    227  await negotiate(offerer, answerer);
    228 
    229  const [dco, dca] = await pairPromise;
    230 
    231  const id = dco.id;
    232  assert_equals(dca.id, id, 'ids should match');
    233 
    234  async function waitForCloseThenReCreate(channel, pc) {
    235    await new Promise(r => channel.onclose = r);
    236    return pc.createDataChannel('bar', {negotiated: true, id});
    237  }
    238 
    239  dco.close();
    240 
    241  const [dco2, dca2] = await Promise.all([
    242    waitForCloseThenReCreate(dco, offerer),
    243    waitForCloseThenReCreate(dca, answerer)
    244  ]);
    245 
    246  assert_equals(dco2.id, id, 'Id should be reused (closer)');
    247  assert_equals(dca2.id, id, 'Id should be reused (closee)');
    248 }, 'Test that ids are reusable after close event');
    249 
    250 /*
    251  Create 32768 (client), 32767 (server) channels to make sure all ids are exhausted AFTER
    252  establishing a peer connection.
    253 
    254  6.1.  createDataChannel
    255    21. If the [[DataChannelId]] slot is null (due to no ID being passed into
    256        createDataChannel, or [[Negotiated]] being false), and the DTLS role of the SCTP
    257        transport has already been negotiated, then initialize [[DataChannelId]] to a value
    258        generated by the user agent, according to [RTCWEB-DATA-PROTOCOL], and skip
    259        to the next step. If no available ID could be generated, or if the value of the
    260        [[DataChannelId]] slot is being used by an existing RTCDataChannel, throw an
    261        OperationError exception.
    262 */
    263 /*
    264 TODO: Improve test coverage for RTCSctpTransport.maxChannels.
    265 TODO: Improve test coverage for exhausting channel cases.
    266 */
    267 
    268 /*
    269  Create 32768 (client), 32767 (server) channels to make sure all ids are exhausted BEFORE
    270  establishing a peer connection.
    271 
    272  Be aware that late channel id assignment can currently fail in many places not covered by the
    273  spec, see: https://github.com/w3c/webrtc-pc/issues/1818
    274 
    275  4.4.1.6.
    276    2.2.6.  If description negotiates the DTLS role of the SCTP transport, and there is an
    277            RTCDataChannel with a null id, then generate an ID according to [RTCWEB-DATA-PROTOCOL].
    278            If no available ID could be generated, then run the following steps:
    279      1.    Let channel be the RTCDataChannel object for which an ID could not be generated.
    280      2.    Set channel's [[ReadyState]] slot to "closed".
    281      3.    Fire an event named error with an OperationError exception at channel.
    282      4.    Fire a simple event named close at channel.
    283 */
    284 /* TEST DISABLED - it takes so long, it times out.
    285 promise_test(async (t) => {
    286  const resolver = new Resolver();
    287  // Takes the DTLS server role
    288  const pc1 = new RTCPeerConnection();
    289  // Takes the DTLS client role
    290  const pc2 = new RTCPeerConnection();
    291  t.add_cleanup(() => pc1.close());
    292  t.add_cleanup(() => pc2.close());
    293 
    294  exchangeIceCandidates(pc1, pc2);
    295  const dcs = [];
    296  const ids = new UniqueSet();
    297  let nExpected = 0;
    298  let nActualCloses = 0;
    299  let nActualErrors = 0;
    300 
    301  const maybeDone = t.step_func(() => {
    302    if (nExpected === nActualCloses && nExpected === nActualErrors) {
    303      resolver.resolve();
    304    }
    305  });
    306 
    307  // Create 65535+2 channels (since 65535 streams is a SHOULD, we may have less than that.)
    308  // Create two extra channels to possibly trigger the steps in the description.
    309  //
    310  // Note: Following the spec strictly would assume that this cannot fail. But in reality it will
    311  //       fail because the implementation knows how many streams it supports. What it doesn't
    312  //       know is how many streams the other peer supports (e.g. what will be negotiated).
    313  for (let i = 0; i < (nStreams + 2); ++i) {
    314    let dc;
    315    try {
    316      const pc = i % 2 === 1 ? pc1 : pc2;
    317      dc = pc.createDataChannel('this is going to be fun');
    318      dc.onclose = t.step_func(() => {
    319        ++nActualCloses;
    320        maybeDone();
    321      });
    322      dc.onerror = t.step_func((e) => {
    323        assert_true(e instanceof RTCError, 'Expect error object to be instance of RTCError');
    324        assert_equals(e.error, 'sctp-failure', "Expect error to be of type 'sctp-failure'");
    325        ++nActualErrors;
    326        maybeDone();
    327      });
    328    } catch (e) {
    329      assert_equals(e.name, 'OperationError', 'Fail on creation should throw OperationError');
    330      break;
    331    }
    332    assert_equals(dc.id, null, 'Channel id must be null before DTLS role has been determined');
    333    assert_not_equals(dc.readyState, 'closed',
    334      'Channel may not be closed before connection establishment');
    335    dcs.push([dc, i % 2 === 1]);
    336  }
    337 
    338  await exchangeOfferAnswer(pc1, pc2, {
    339    offer: (offer) => {
    340      // Ensure pc1 takes the server role
    341      assert_true(offer.sdp.includes('actpass') || offer.sdp.includes('passive'),
    342        'pc1 must take the DTLS server role');
    343      return offer;
    344    },
    345    answer: (answer) => {
    346      // Ensure pc2 takes the client role
    347      // Note: It very likely will choose 'active' itself
    348      answer.sdp = answer.sdp.replace('actpass', 'active');
    349      assert_true(answer.sdp.includes('active'), 'pc2 must take the DTLS client role');
    350      return answer;
    351    },
    352  });
    353 
    354  // Since the spec does not define a specific order to which channels may fail if an ID could
    355  // not be generated, any of the channels may be affected by the steps of the description.
    356  for (const [dc, odd] of dcs) {
    357    if (dc.readyState !== 'closed') {
    358      assert_equals(dc.id % 2, odd ? 1 : 0,
    359        `Channels created by the DTLS ${odd ? 'server' : 'client'} role must be
    360        ${odd ? 'odd' : 'even'} (was ${dc.id})`);
    361      ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
    362    } else {
    363      ++nExpected;
    364    }
    365  }
    366 
    367  // Try creating one further channel on both sides. The attempt should fail since all IDs are
    368  // taken. If one ID is available, the implementation probably miscounts (or I did in the test).
    369  assert_throws_dom('OperationError', () =>
    370    pc1.createDataChannel('this is too exhausting!'));
    371  assert_throws_dom('OperationError', () =>
    372    pc2.createDataChannel('this is too exhausting!'));
    373 
    374  maybeDone();
    375  await resolver;
    376 }, 'Channel ID exhaustion handling (before and after connection establishment)');
    377 
    378 END DISABLED TEST */
    379 
    380 </script>