tor-browser

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

test_dataChannel_dcsctp_interop.html (11707B)


      1 <!DOCTYPE HTML>
      2 <html>
      3  <script type="application/javascript" src="pc.js"></script>
      4 <head>
      5 </head>
      6 <body>
      7 <pre id="test">
      8 <script type="application/javascript">
      9  createHTML({
     10    bug: "1927886",
     11    title: "Test that usrsctp and dcsctp interop"
     12  });
     13 
     14  async function createChannel(pc, impl, name, options) {
     15    async function create() {
     16      return pc.createDataChannel(name, options);
     17    }
     18 
     19    if (impl == "usrsctp") {
     20      return withPrefs([["media.peerconnection.sctp.use_dcsctp", false]], create);
     21    } else if (impl == "dcsctp" ){
     22      return withPrefs([["media.peerconnection.sctp.use_dcsctp", true]], create);
     23    } else {
     24      throw new Error(`Unknown sctp impl "${impl}"`);
     25    }
     26  }
     27 
     28  async function connect(offerer, answerer) {
     29    offerer.onicecandidate = e => answerer.addIceCandidate(e.candidate);
     30    answerer.onicecandidate = e => offerer.addIceCandidate(e.candidate);
     31    const offererConnected = new Promise(r => {
     32      offerer.oniceconnectionstatechange = () => {
     33        if (offerer.iceConnectionState == 'connected') {
     34          r();
     35        }
     36      };
     37    });
     38 
     39    const answererConnected = new Promise(r => {
     40      answerer.oniceconnectionstatechange = () => {
     41        if (answerer.iceConnectionState == 'connected') {
     42          r();
     43        }
     44      };
     45    });
     46 
     47    await offerer.setLocalDescription();
     48    await answerer.setRemoteDescription(offerer.localDescription);
     49    await answerer.setLocalDescription();
     50    await offerer.setRemoteDescription(answerer.localDescription);
     51    await offererConnected;
     52    await answererConnected;
     53  }
     54 
     55  async function channelOpen(dc) {
     56    return new Promise((res, rej) => {
     57      if (dc.readyState == "open") {
     58        res();
     59      } else {
     60        dc.onopen = res;
     61        dc.onclose = rej;
     62        dc.onerror = rej;
     63      }
     64    });
     65  }
     66 
     67  async function channelCreated(pc, label) {
     68    return new Promise((res, rej) => {
     69      pc.addEventListener("datachannel", (e) => {
     70        if (label === undefined || label == e.channel.label) {
     71          res(e);
     72        }
     73      });
     74      pc.addEventListener("connectionstatechange", e => {
     75        if (pc.connectionState == "closed") {
     76          rej("PC closed before channel was created");
     77        }
     78      });
     79    });
     80  }
     81 
     82  async function sendRecvMessages(sender, receiver, messages) {
     83    if (!Array.isArray(messages)) {
     84      messages = [messages];
     85    }
     86 
     87    const received = [];
     88 
     89    messages.forEach(msg => sender.send(msg));
     90 
     91    return new Promise((res, rej) => {
     92      receiver.onmessage = (e) => {
     93        received.push(e.data);
     94        if (received.length == messages.length) {
     95          res(received);
     96        }
     97      };
     98      receiver.onerror = rej;
     99      receiver.onclose = rej;
    100    });
    101  }
    102 
    103  async function sendRecvMessagesUnreliable(sender, receiver, messages) {
    104    if (!Array.isArray(messages)) {
    105      messages = [messages];
    106    }
    107 
    108    const received = [];
    109 
    110    messages.forEach(msg => sender.send(msg));
    111 
    112    return new Promise((res, rej) => {
    113      // Wait at most a couple of seconds; there's no guarantee all will arrive
    114      setTimeout(() => res(received), 2000);
    115      receiver.onmessage = (e) => {
    116        received.push(e.data);
    117        if (received.length == messages.length) {
    118          res(received);
    119        }
    120      };
    121      receiver.onerror = rej;
    122      receiver.onclose = rej;
    123    });
    124  }
    125 
    126  function checkMessages(expected, observed, options) {
    127    let expectedCopy;
    128    // Default value for ordered is true. Why they didn't use "unordered" with
    129    // a default of false is anyone's guess.
    130    if (options && options.ordered === false) {
    131      expectedCopy = expected.toSorted();
    132      observed.sort();
    133    } else {
    134      expectedCopy = new Array(...expected.values());
    135    }
    136 
    137    if (options &&
    138         (options.maxRetransmits !== undefined ||
    139          options.maxPacketLifetime !== undefined)) {
    140      // Realistically, what can we check here? Packet loss should be rare, but
    141      // with lots of messages it can happen. Maybe just sanity check that at
    142      // least one got through, and that everything we observed could be found
    143      // in what we sent, in the right order?
    144      ok(observed.length,
    145        "Should have received at least one of the sent messages");
    146      ok(observed.length <= expectedCopy.length,
    147        `Should not have received more messages (${observed.length}) than we sent (${expectedCopy.length})`);
    148      observed.forEach(msg => {
    149        const index = expectedCopy.indexOf(msg);
    150        if (index < 0) {
    151          ok(false, `We observed ${msg}, so we should have sent it.`);
    152        }
    153        expectedCopy = expectedCopy.slice(index + 1);
    154      });
    155    } else {
    156      isDeeply(observed, expectedCopy, `Expected to have received the messages we sent`);
    157    }
    158  }
    159 
    160  async function checkSendRecv(sender, receiver, msgs, options) {
    161    let observedMsgs = [];
    162 
    163    if (options &&
    164      (options.maxRetransmits !== undefined ||
    165        options.maxPacketLifetime !== undefined)) {
    166      observedMsgs = await sendRecvMessagesUnreliable(sender, receiver, msgs);
    167    } else {
    168      observedMsgs = await sendRecvMessages(sender, receiver, msgs);
    169    }
    170 
    171    checkMessages(msgs, observedMsgs, options);
    172  }
    173 
    174  async function checkSingleChannel(impl1, impl2, msgs, options) {
    175    if (!Array.isArray(msgs)) {
    176      msgs = [msgs];
    177    }
    178    const offerer = new RTCPeerConnection();
    179    const answerer = new RTCPeerConnection();
    180    const dc1 = await createChannel(offerer, impl1, "test", options);
    181    const dcsctp = impl2 == "dcsctp";
    182 
    183    await withPrefs([["media.peerconnection.sctp.use_dcsctp", dcsctp]], async () => {
    184 
    185      const dc1Open = channelOpen(dc1);
    186      let dc2;
    187      if (options && options.negotiated) {
    188        dc2 = await createChannel(answerer, impl2, "test", options);
    189        await connect(offerer, answerer);
    190      } else {
    191        const dc2Created = channelCreated(answerer);
    192        await connect(offerer, answerer);
    193        dc2 = (await dc2Created).channel;
    194      }
    195      await channelOpen(dc2);
    196      await dc1Open;
    197 
    198      // Ping pong to ensure that any open acks have made it across.
    199      // Even if we're in unreliable mode, we should not see packet loss on
    200      // something this small.
    201      await sendRecvMessages(dc1, dc2, ["ping"]);
    202      await sendRecvMessages(dc2, dc1, ["pong"]);
    203 
    204      await checkSendRecv(dc1, dc2, msgs, options);
    205      await checkSendRecv(dc2, dc1, msgs, options);
    206    });
    207  }
    208 
    209  async function checkMultiChannel(impl1, impl2, msgs, optionsArray) {
    210    if (!Array.isArray(msgs)) {
    211      msgs = [msgs];
    212    }
    213    const offerer = new RTCPeerConnection();
    214    const answerer = new RTCPeerConnection();
    215 
    216    const dcs1 = await Promise.all(optionsArray.map(
    217        (options, index) => createChannel(offerer, impl1, `test_${index}`, options)));
    218    const dcsctp = impl2 == "dcsctp";
    219 
    220    await withPrefs([["media.peerconnection.sctp.use_dcsctp", dcsctp]], async () => {
    221      const dcs1Open = dcs1.map(dc => channelOpen(dc));
    222      const dcs2Created = optionsArray.map(async (options, index) => {
    223        if (options && options.negotiated) {
    224          return createChannel(answerer, impl2, `test_${index}`, options);
    225        } else {
    226          return (await channelCreated(answerer, `test_${index}`)).channel;
    227        }
    228      });
    229      await connect(offerer, answerer);
    230      const dcs2 = await Promise.all(dcs2Created);
    231      await Promise.all(dcs2.map(dc => channelOpen(dc)));
    232      await Promise.all(dcs1Open);
    233 
    234      // We are ordering these by label, but make sure the ids line up too
    235      isDeeply(dcs1.map(dc => dc.id), dcs2.map(dc => dc.id),
    236        "Channels should have the same ids");
    237 
    238      // Ping pong to give acks time to come across.
    239      // Even if we're in unreliable mode, we should not see packet loss on
    240      // something this small.
    241      await sendRecvMessages(dcs1[0], dcs2[0], ["ping"]);
    242      await sendRecvMessages(dcs2[0], dcs1[0], ["pong"]);
    243 
    244      await Promise.all(dcs1.map(
    245        (dc, index) => checkSendRecv(dc, dcs2[index], msgs, optionsArray[index])));
    246      await Promise.all(dcs2.map(
    247        (dc, index) => checkSendRecv(dc, dcs1[index], msgs, optionsArray[index])));
    248    });
    249  }
    250 
    251  async function runTestVariants(func, ...args) {
    252    for (const [impl1, impl2] of
    253      [["usrsctp", "usrsctp"], ["dcsctp", "dcsctp"],
    254       ["usrsctp", "dcsctp"], ["dcsctp", "usrsctp"]]) {
    255      info(`Running variant ${impl1}/${impl2}`);
    256      await func(impl1, impl2, ...args);
    257      info(`Done running variant ${impl1}/${impl2}`);
    258    }
    259  }
    260 
    261  const tests = [
    262    async function testBasicOperation() {
    263      await runTestVariants(checkSingleChannel, "test_message");
    264    },
    265    async function testNegotiated() {
    266      await runTestVariants(checkSingleChannel, "test_message",
    267        {negotiated: true, id:42});
    268    },
    269    async function testLargeMessage() {
    270      const largeMessage = "test".repeat(100000);
    271      await runTestVariants(checkSingleChannel, largeMessage);
    272    },
    273    async function testManyMessages() {
    274      const messageArray = [];
    275      for (let i = 0; i < 1000; i++) {
    276        messageArray.push(`test_${i}`);
    277      }
    278      await runTestVariants(checkSingleChannel, messageArray);
    279    },
    280    async function testUnordered() {
    281      const messageArray = [];
    282      for (let i = 0; i < 1000; i++) {
    283        messageArray.push(`test_${i}`);
    284      }
    285      await runTestVariants(checkSingleChannel, messageArray, {ordered: false});
    286    },
    287    async function testNoRetransmit() {
    288      await runTestVariants(checkSingleChannel, "test_message",
    289        {maxRetransmits: 0});
    290    },
    291    async function testNoTTL() {
    292      await runTestVariants(checkSingleChannel, "test_message",
    293        {maxPacketLifetime: 0});
    294    },
    295    async function test1Retransmit() {
    296      const messageArray = [];
    297      for (let i = 0; i < 1000; i++) {
    298        messageArray.push(`test_${i}`);
    299      }
    300      await runTestVariants(checkSingleChannel, messageArray,
    301        {maxRetransmits: 1});
    302    },
    303    async function testShortTTL() {
    304      const messageArray = [];
    305      for (let i = 0; i < 1000; i++) {
    306        messageArray.push(`test_${i}`);
    307      }
    308      await runTestVariants(checkSingleChannel, messageArray,
    309        {maxPacketLifetime: 1});
    310    },
    311    async function testLargeStreamId() {
    312      await runTestVariants(checkSingleChannel, "test_message",
    313        {negotiated: true, id: 2000});
    314    },
    315    async function testMultiChannel() {
    316      await runTestVariants(checkMultiChannel, "test_message", [{}, {}]);
    317    },
    318    async function testManyChannel() {
    319      const bigOptionsArray = [];
    320      for (let i = 0; i < 100; i++) {
    321        bigOptionsArray.push({});
    322      }
    323      await runTestVariants(checkMultiChannel, "test_message", bigOptionsArray);
    324    },
    325    async function testMixedNegotiated() {
    326      await runTestVariants(checkMultiChannel, "test_message",
    327        [{negotiated: true, id:42}, {}]);
    328    },
    329    async function testMixedReliability() {
    330      await runTestVariants(checkMultiChannel,
    331        ["test_message1", "test_message2"],
    332        [{maxRetransmits: 0}, {maxRetransmits: 1}, {maxPacketLifetime: 0},
    333         {maxPacketLifetime: 1}, {}]);
    334    },
    335    async function testMixedOrdered() {
    336      await runTestVariants(checkMultiChannel,
    337        ["test_message1", "test_message2"],
    338        [{ordered: false}, {ordered: true}, {}]);
    339    },
    340  ];
    341 
    342  runNetworkTest(async () => {
    343    for (const test of tests) {
    344      info(`Running test: ${test.name}`);
    345      await test();
    346      info(`Done running test: ${test.name}`);
    347      // Make sure we don't build up a pile of GC work, and also get PCImpl to
    348      // print their timecards.
    349      await new Promise(r => SpecialPowers.exactGC(r));
    350    }
    351  });
    352 
    353 </script>
    354 </pre>
    355 </body>
    356 </html>