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>