diff options
Diffstat (limited to '')
-rw-r--r-- | testing/web-platform/tests/webrtc/RTCDataChannel-id.html | 345 |
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> |