summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCDataChannel-id.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCDataChannel-id.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCDataChannel-id.html345
1 files changed, 345 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCDataChannel-id.html b/testing/web-platform/tests/webrtc/RTCDataChannel-id.html
new file mode 100644
index 0000000000..10dc5eacb9
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCDataChannel-id.html
@@ -0,0 +1,345 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCDataChannel id attribute</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+// Test is based on the following revision:
+// https://rawgit.com/w3c/webrtc-pc/1cc5bfc3ff18741033d804c4a71f7891242fb5b3/webrtc.html
+
+// This is the maximum number of streams, NOT the maximum stream ID (which is 65534)
+// See: https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.2
+const nStreams = 65535;
+
+/*
+ 6.1.
+ 21. If the [[DataChannelId]] slot is null (due to no ID being passed into
+ createDataChannel, or [[Negotiated]] being false), and the DTLS role of the SCTP
+ transport has already been negotiated, then initialize [[DataChannelId]] to a value
+ generated by the user agent, according to [RTCWEB-DATA-PROTOCOL] [...]
+ */
+promise_test(async (t) => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc1 = pc.createDataChannel('');
+ const ids = new UniqueSet();
+
+ const offer = await pc.createOffer();
+ await pc.setLocalDescription(offer);
+ // Turn our own offer SDP into valid answer SDP by setting the DTLS role to
+ // "active".
+ const answer = {
+ type: 'answer',
+ sdp: pc.localDescription.sdp.replace('actpass', 'active')
+ };
+ await pc.setRemoteDescription(answer);
+
+ // Since the remote description had an 'active' DTLS role, we're the server
+ // and should use odd data channel IDs, according to rtcweb-data-channel.
+ assert_equals(dc1.id % 2, 1,
+ `Channel created by the DTLS server role must be odd (was ${dc1.id})`);
+ const dc2 = pc.createDataChannel('another');
+ assert_equals(dc2.id % 2, 1,
+ `Channel created by the DTLS server role must be odd (was ${dc2.id})`);
+
+ // Ensure IDs are unique
+ ids.add(dc1.id, `Channel ID ${dc1.id} should be unique`);
+ ids.add(dc2.id, `Channel ID ${dc2.id} should be unique`);
+}, 'DTLS client uses odd data channel IDs');
+
+promise_test(async (t) => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc1 = pc.createDataChannel('');
+ const ids = new UniqueSet();
+
+ const offer = await pc.createOffer();
+ await pc.setLocalDescription(offer);
+ // Turn our own offer SDP into valid answer SDP by setting the DTLS role to
+ // 'passive'.
+ const answer = {
+ type: 'answer',
+ sdp: pc.localDescription.sdp.replace('actpass', 'passive')
+ };
+ await pc.setRemoteDescription(answer);
+
+ // Since the remote description had a 'passive' DTLS role, we're the client
+ // and should use even data channel IDs, according to rtcweb-data-channel.
+ assert_equals(dc1.id % 2, 0,
+ `Channel created by the DTLS client role must be even (was ${dc1.id})`);
+ const dc2 = pc.createDataChannel('another');
+ assert_equals(dc2.id % 2, 0,
+ `Channel created by the DTLS client role must be even (was ${dc1.id})`);
+
+ // Ensure IDs are unique
+ ids.add(dc1.id, `Channel ID ${dc1.id} should be unique`);
+ ids.add(dc2.id, `Channel ID ${dc2.id} should be unique`);
+}, 'DTLS server uses even data channel IDs');
+
+/*
+ Checks that the id is ignored if "negotiated" is false.
+ See section 6.1, createDataChannel step 13.
+ */
+promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const dc1 = pc1.createDataChannel('', {
+ negotiated: false,
+ id: 42
+ });
+ dc1.onopen = t.step_func(() => {
+ dc1.send(':(');
+ });
+
+ const dc2 = pc2.createDataChannel('', {
+ negotiated: false,
+ id: 42
+ });
+ // ID should be null prior to negotiation.
+ assert_equals(dc1.id, null);
+ assert_equals(dc2.id, null);
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+ // We should now have 2 datachannels with different IDs.
+ // At least one of the datachannels should not be 42.
+ // If one has the value 42, it's an accident; if both have,
+ // they are the same datachannel, and it's a bug.
+ assert_false(dc1.id == 42 && dc2.id == 42);
+}, 'In-band negotiation with a specific ID should not work');
+
+/*
+ Check if the implementation still follows the odd/even role correctly if we annoy it with
+ negotiated channels not following that rule.
+
+ Note: This test assumes that the implementation can handle a minimum of 40 data channels.
+ */
+promise_test(async (t) => {
+ // Takes the DTLS server role
+ const pc1 = new RTCPeerConnection();
+ // Takes the DTLS client role
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ exchangeIceCandidates(pc1, pc2);
+ const dcs = [];
+ const negotiatedDcs = [];
+ const ids = new UniqueSet();
+
+ // Create 10 DCEP-negotiated channels with pc1
+ // Note: These should not have any associated valid ID at this point
+ for (let i = 0; i < 10; ++i) {
+ const dc = pc1.createDataChannel('before-connection');
+ assert_equals(dc.id, null, 'Channel id must be null before DTLS role has been determined');
+ dcs.push(dc);
+ }
+
+ // Create 10 negotiated channels with pc1 violating the odd/even rule
+ for (let id = 0; id < 20; id += 2) {
+ const dc = pc1.createDataChannel(`negotiated-not-odd-${id}-before-connection`, {
+ negotiated: true,
+ id: id,
+ });
+ assert_equals(dc.id, id, 'Channel id must be set before DTLS role has been determined when negotiated is true');
+ negotiatedDcs.push([dc, id]);
+ ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
+ }
+
+ await exchangeOfferAnswer(pc1, pc2, {
+ offer: (offer) => {
+ // Ensure pc1 takes the server role
+ assert_true(offer.sdp.includes('actpass') || offer.sdp.includes('passive'),
+ 'pc1 must take the DTLS server role');
+ return offer;
+ },
+ answer: (answer) => {
+ // Ensure pc2 takes the client role
+ // Note: It very likely will choose 'active' itself
+ answer.sdp = answer.sdp.replace('actpass', 'active');
+ assert_true(answer.sdp.includes('active'), 'pc2 must take the DTLS client role');
+ return answer;
+ },
+ });
+
+ for (const dc of dcs) {
+ assert_equals(dc.id % 2, 1,
+ `Channel created by the DTLS server role must be odd (was ${dc.id})`);
+ ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
+ }
+
+ // Create 10 channels with pc1
+ for (let i = 0; i < 10; ++i) {
+ const dc = pc1.createDataChannel('after-connection');
+ assert_equals(dc.id % 2, 1,
+ `Channel created by the DTLS server role must be odd (was ${dc.id})`);
+ dcs.push(dc);
+ ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
+ }
+
+ // Create 10 negotiated channels with pc1 violating the odd/even rule
+ for (let i = 0; i < 10; ++i) {
+ // Generate a valid even ID that has not been taken, yet.
+ let id = 20;
+ while (ids.has(id)) {
+ id += 2;
+ }
+ const dc = pc1.createDataChannel(`negotiated-not-odd-${i}-after-connection`, {
+ negotiated: true,
+ id: id,
+ });
+ negotiatedDcs.push([dc, id]);
+ ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
+ }
+
+ // Since we've added new channels, let's check again that the odd/even role is not violated
+ for (const dc of dcs) {
+ assert_equals(dc.id % 2, 1,
+ `Channel created by the DTLS server role must be odd (was ${dc.id})`);
+ }
+
+ // Let's also make sure the negotiated channels have kept their ID
+ for (const [dc, id] of negotiatedDcs) {
+ assert_equals(dc.id, id, 'Negotiated channels should keep their assigned ID');
+ }
+}, 'Odd/even role should not be violated when mixing with negotiated channels');
+
+/*
+ Create 32768 (client), 32767 (server) channels to make sure all ids are exhausted AFTER
+ establishing a peer connection.
+
+ 6.1. createDataChannel
+ 21. If the [[DataChannelId]] slot is null (due to no ID being passed into
+ createDataChannel, or [[Negotiated]] being false), and the DTLS role of the SCTP
+ transport has already been negotiated, then initialize [[DataChannelId]] to a value
+ generated by the user agent, according to [RTCWEB-DATA-PROTOCOL], and skip
+ to the next step. If no available ID could be generated, or if the value of the
+ [[DataChannelId]] slot is being used by an existing RTCDataChannel, throw an
+ OperationError exception.
+ */
+/*
+ TODO: Improve test coverage for RTCSctpTransport.maxChannels.
+ TODO: Improve test coverage for exhausting channel cases.
+ */
+
+/*
+ Create 32768 (client), 32767 (server) channels to make sure all ids are exhausted BEFORE
+ establishing a peer connection.
+
+ Be aware that late channel id assignment can currently fail in many places not covered by the
+ spec, see: https://github.com/w3c/webrtc-pc/issues/1818
+
+ 4.4.1.6.
+ 2.2.6. If description negotiates the DTLS role of the SCTP transport, and there is an
+ RTCDataChannel with a null id, then generate an ID according to [RTCWEB-DATA-PROTOCOL].
+ If no available ID could be generated, then run the following steps:
+ 1. Let channel be the RTCDataChannel object for which an ID could not be generated.
+ 2. Set channel's [[ReadyState]] slot to "closed".
+ 3. Fire an event named error with an OperationError exception at channel.
+ 4. Fire a simple event named close at channel.
+ */
+/* TEST DISABLED - it takes so long, it times out.
+promise_test(async (t) => {
+ const resolver = new Resolver();
+ // Takes the DTLS server role
+ const pc1 = new RTCPeerConnection();
+ // Takes the DTLS client role
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ exchangeIceCandidates(pc1, pc2);
+ const dcs = [];
+ const ids = new UniqueSet();
+ let nExpected = 0;
+ let nActualCloses = 0;
+ let nActualErrors = 0;
+
+ const maybeDone = t.step_func(() => {
+ if (nExpected === nActualCloses && nExpected === nActualErrors) {
+ resolver.resolve();
+ }
+ });
+
+ // Create 65535+2 channels (since 65535 streams is a SHOULD, we may have less than that.)
+ // Create two extra channels to possibly trigger the steps in the description.
+ //
+ // Note: Following the spec strictly would assume that this cannot fail. But in reality it will
+ // fail because the implementation knows how many streams it supports. What it doesn't
+ // know is how many streams the other peer supports (e.g. what will be negotiated).
+ for (let i = 0; i < (nStreams + 2); ++i) {
+ let dc;
+ try {
+ const pc = i % 2 === 1 ? pc1 : pc2;
+ dc = pc.createDataChannel('this is going to be fun');
+ dc.onclose = t.step_func(() => {
+ ++nActualCloses;
+ maybeDone();
+ });
+ dc.onerror = t.step_func((e) => {
+ assert_true(e instanceof RTCError, 'Expect error object to be instance of RTCError');
+ assert_equals(e.error, 'sctp-failure', "Expect error to be of type 'sctp-failure'");
+ ++nActualErrors;
+ maybeDone();
+ });
+ } catch (e) {
+ assert_equals(e.name, 'OperationError', 'Fail on creation should throw OperationError');
+ break;
+ }
+ assert_equals(dc.id, null, 'Channel id must be null before DTLS role has been determined');
+ assert_not_equals(dc.readyState, 'closed',
+ 'Channel may not be closed before connection establishment');
+ dcs.push([dc, i % 2 === 1]);
+ }
+
+ await exchangeOfferAnswer(pc1, pc2, {
+ offer: (offer) => {
+ // Ensure pc1 takes the server role
+ assert_true(offer.sdp.includes('actpass') || offer.sdp.includes('passive'),
+ 'pc1 must take the DTLS server role');
+ return offer;
+ },
+ answer: (answer) => {
+ // Ensure pc2 takes the client role
+ // Note: It very likely will choose 'active' itself
+ answer.sdp = answer.sdp.replace('actpass', 'active');
+ assert_true(answer.sdp.includes('active'), 'pc2 must take the DTLS client role');
+ return answer;
+ },
+ });
+
+ // Since the spec does not define a specific order to which channels may fail if an ID could
+ // not be generated, any of the channels may be affected by the steps of the description.
+ for (const [dc, odd] of dcs) {
+ if (dc.readyState !== 'closed') {
+ assert_equals(dc.id % 2, odd ? 1 : 0,
+ `Channels created by the DTLS ${odd ? 'server' : 'client'} role must be
+ ${odd ? 'odd' : 'even'} (was ${dc.id})`);
+ ids.add(dc.id, `Channel ID ${dc.id} should be unique`);
+ } else {
+ ++nExpected;
+ }
+ }
+
+ // Try creating one further channel on both sides. The attempt should fail since all IDs are
+ // taken. If one ID is available, the implementation probably miscounts (or I did in the test).
+ assert_throws_dom('OperationError', () =>
+ pc1.createDataChannel('this is too exhausting!'));
+ assert_throws_dom('OperationError', () =>
+ pc2.createDataChannel('this is too exhausting!'));
+
+ maybeDone();
+ await resolver;
+}, 'Channel ID exhaustion handling (before and after connection establishment)');
+
+END DISABLED TEST */
+
+</script>