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>