RTCIceTransport-extension.https.html (13896B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCIceTransport-extensions.https.html</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="RTCIceTransport-extension-helper.js"></script> 7 <script> 8 'use strict'; 9 10 // These tests are based on the following extension specification: 11 // https://w3c.github.io/webrtc-ice/ 12 13 // The following helper functions are called from 14 // RTCIceTransport-extension-helper.js: 15 // makeIceTransport 16 // makeGatherAndStartTwoIceTransports 17 18 const ICE_UFRAG = 'u'.repeat(4); 19 const ICE_PWD = 'p'.repeat(22); 20 21 test(() => { 22 const iceTransport = new RTCIceTransport(); 23 }, 'RTCIceTransport constructor does not throw'); 24 25 test(() => { 26 const iceTransport = new RTCIceTransport(); 27 assert_equals(iceTransport.role, null, 'Expect role to be null'); 28 assert_equals(iceTransport.state, 'new', `Expect state to be 'new'`); 29 assert_equals(iceTransport.gatheringState, 'new', 30 `Expect gatheringState to be 'new'`); 31 assert_array_equals(iceTransport.getLocalCandidates(), [], 32 'Expect no local candidates'); 33 assert_array_equals(iceTransport.getRemoteCandidates(), [], 34 'Expect no remote candidates'); 35 assert_equals(iceTransport.getSelectedCandidatePair(), null, 36 'Expect no selected candidate pair'); 37 assert_not_equals(iceTransport.getLocalParameters(), null, 38 'Expect local parameters generated'); 39 assert_equals(iceTransport.getRemoteParameters(), null, 40 'Expect no remote parameters'); 41 }, 'RTCIceTransport initial properties are set'); 42 43 test(t => { 44 const iceTransport = makeIceTransport(t); 45 assert_throws_js(TypeError, () => 46 iceTransport.gather({ iceServers: null })); 47 }, 'gather() with { iceServers: null } should throw TypeError'); 48 49 test(t => { 50 const iceTransport = makeIceTransport(t); 51 iceTransport.gather({ iceServers: undefined }); 52 }, 'gather() with { iceServers: undefined } should succeed'); 53 54 test(t => { 55 const iceTransport = makeIceTransport(t); 56 iceTransport.gather({ iceServers: [{ 57 urls: ['turns:turn.example.org', 'turn:turn.example.net'], 58 username: 'user', 59 credential: 'cred', 60 }] }); 61 }, 'gather() with one turns server, one turn server, username, credential' + 62 ' should succeed'); 63 64 test(t => { 65 const iceTransport = makeIceTransport(t); 66 iceTransport.gather({ iceServers: [{ 67 urls: ['stun:stun1.example.net', 'stun:stun2.example.net'], 68 }] }); 69 }, 'gather() with 2 stun servers should succeed'); 70 71 test(t => { 72 const iceTransport = makeIceTransport(t); 73 iceTransport.stop(); 74 assert_throws_dom('InvalidStateError', () => iceTransport.gather({})); 75 }, 'gather() throws if closed'); 76 77 test(t => { 78 const iceTransport = makeIceTransport(t); 79 iceTransport.gather({}); 80 assert_equals(iceTransport.gatheringState, 'gathering'); 81 }, `gather() transitions gatheringState to 'gathering'`); 82 83 test(t => { 84 const iceTransport = makeIceTransport(t); 85 iceTransport.gather({}); 86 assert_throws_dom('InvalidStateError', () => iceTransport.gather({})); 87 }, 'gather() throws if called twice'); 88 89 promise_test(async t => { 90 const iceTransport = makeIceTransport(t); 91 const watcher = new EventWatcher(t, iceTransport, 'gatheringstatechange'); 92 iceTransport.gather({}); 93 await watcher.wait_for('gatheringstatechange'); 94 assert_equals(iceTransport.gatheringState, 'complete'); 95 }, `eventually transition gatheringState to 'complete'`); 96 97 promise_test(async t => { 98 const iceTransport = makeIceTransport(t); 99 const watcher = new EventWatcher(t, iceTransport, 100 [ 'icecandidate', 'gatheringstatechange' ]); 101 iceTransport.gather({}); 102 let candidate; 103 do { 104 (({ candidate } = await watcher.wait_for('icecandidate'))); 105 } while (candidate !== null); 106 assert_equals(iceTransport.gatheringState, 'gathering'); 107 await watcher.wait_for('gatheringstatechange'); 108 assert_equals(iceTransport.gatheringState, 'complete'); 109 }, 'onicecandidate fires with null candidate before gatheringState' + 110 ` transitions to 'complete'`); 111 112 promise_test(async t => { 113 const iceTransport = makeIceTransport(t); 114 const watcher = new EventWatcher(t, iceTransport, 'icecandidate'); 115 iceTransport.gather({}); 116 const { candidate } = await watcher.wait_for('icecandidate'); 117 assert_not_equals(candidate.candidate, ''); 118 assert_array_equals(iceTransport.getLocalCandidates(), [candidate]); 119 }, 'gather() returns at least one host candidate'); 120 121 promise_test(async t => { 122 const iceTransport = makeIceTransport(t); 123 const watcher = new EventWatcher(t, iceTransport, 'icecandidate'); 124 iceTransport.gather({ gatherPolicy: 'relay' }); 125 const { candidate } = await watcher.wait_for('icecandidate'); 126 assert_equals(candidate, null); 127 assert_array_equals(iceTransport.getLocalCandidates(), []); 128 }, `gather() returns no candidates with { gatherPolicy: 'relay'} and no turn` + 129 ' servers'); 130 131 const dummyRemoteParameters = { 132 usernameFragment: ICE_UFRAG, 133 password: ICE_PWD, 134 }; 135 136 test(() => { 137 const iceTransport = new RTCIceTransport(); 138 iceTransport.stop(); 139 assert_throws_dom('InvalidStateError', 140 () => iceTransport.start(dummyRemoteParameters)); 141 assert_equals(iceTransport.getRemoteParameters(), null); 142 }, `start() throws if closed`); 143 144 test(() => { 145 const iceTransport = new RTCIceTransport(); 146 assert_throws_js(TypeError, () => iceTransport.start({})); 147 assert_throws_js(TypeError, 148 () => iceTransport.start({ usernameFragment: ICE_UFRAG })); 149 assert_throws_js(TypeError, 150 () => iceTransport.start({ password: ICE_PWD })); 151 assert_equals(iceTransport.getRemoteParameters(), null); 152 }, 'start() throws if usernameFragment or password not set'); 153 154 test(() => { 155 const TEST_CASES = [ 156 {usernameFragment: '2sh', description: 'less than 4 characters long'}, 157 { 158 usernameFragment: 'x'.repeat(257), 159 description: 'greater than 256 characters long', 160 }, 161 {usernameFragment: '123\n', description: 'illegal character'}, 162 ]; 163 for (const {usernameFragment, description} of TEST_CASES) { 164 const iceTransport = new RTCIceTransport(); 165 assert_throws_dom( 166 'SyntaxError', 167 () => iceTransport.start({ usernameFragment, password: ICE_PWD }), 168 `illegal usernameFragment (${description}) should throw a SyntaxError`); 169 } 170 }, 'start() throws if usernameFragment does not conform to syntax'); 171 172 test(() => { 173 const TEST_CASES = [ 174 {password: 'x'.repeat(21), description: 'less than 22 characters long'}, 175 { 176 password: 'x'.repeat(257), 177 description: 'greater than 256 characters long', 178 }, 179 {password: ('x'.repeat(21) + '\n'), description: 'illegal character'}, 180 ]; 181 for (const {password, description} of TEST_CASES) { 182 const iceTransport = new RTCIceTransport(); 183 assert_throws_dom( 184 'SyntaxError', 185 () => iceTransport.start({ usernameFragment: ICE_UFRAG, password }), 186 `illegal password (${description}) should throw a SyntaxError`); 187 } 188 }, 'start() throws if password does not conform to syntax'); 189 190 const assert_ice_parameters_equals = (a, b) => { 191 assert_equals(a.usernameFragment, b.usernameFragment, 192 'usernameFragments are equal'); 193 assert_equals(a.password, b.password, 'passwords are equal'); 194 }; 195 196 test(t => { 197 const iceTransport = makeIceTransport(t); 198 iceTransport.start(dummyRemoteParameters); 199 assert_equals(iceTransport.state, 'new'); 200 assert_ice_parameters_equals(iceTransport.getRemoteParameters(), 201 dummyRemoteParameters); 202 }, `start() does not transition state to 'checking' if no remote candidates ` + 203 'added'); 204 205 test(t => { 206 const iceTransport = makeIceTransport(t); 207 iceTransport.start(dummyRemoteParameters); 208 assert_equals(iceTransport.role, 'controlled'); 209 }, `start() with default role sets role attribute to 'controlled'`); 210 211 test(t => { 212 const iceTransport = makeIceTransport(t); 213 iceTransport.start(dummyRemoteParameters, 'controlling'); 214 assert_equals(iceTransport.role, 'controlling'); 215 }, `start() sets role attribute to 'controlling'`); 216 217 const candidate1 = new RTCIceCandidate({ 218 candidate: 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host', 219 sdpMid: '', 220 }); 221 222 test(() => { 223 const iceTransport = new RTCIceTransport(); 224 iceTransport.stop(); 225 assert_throws_dom('InvalidStateError', 226 () => iceTransport.addRemoteCandidate(candidate1)); 227 assert_array_equals(iceTransport.getRemoteCandidates(), []); 228 }, 'addRemoteCandidate() throws if closed'); 229 230 test(() => { 231 const iceTransport = new RTCIceTransport(); 232 assert_throws_dom('OperationError', 233 () => iceTransport.addRemoteCandidate( 234 new RTCIceCandidate({ candidate: 'invalid', sdpMid: '' }))); 235 assert_array_equals(iceTransport.getRemoteCandidates(), []); 236 }, 'addRemoteCandidate() throws on invalid candidate'); 237 238 test(t => { 239 const iceTransport = makeIceTransport(t); 240 iceTransport.addRemoteCandidate(candidate1); 241 iceTransport.start(dummyRemoteParameters); 242 assert_equals(iceTransport.state, 'checking'); 243 assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]); 244 }, `start() transitions state to 'checking' if one remote candidate had been ` + 245 'added'); 246 247 test(t => { 248 const iceTransport = makeIceTransport(t); 249 iceTransport.start(dummyRemoteParameters); 250 iceTransport.addRemoteCandidate(candidate1); 251 assert_equals(iceTransport.state, 'checking'); 252 assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]); 253 }, `addRemoteCandidate() transitions state to 'checking' if start() had been ` + 254 'called before'); 255 256 test(t => { 257 const iceTransport = makeIceTransport(t); 258 iceTransport.start(dummyRemoteParameters); 259 assert_throws_dom('InvalidStateError', 260 () => iceTransport.start(dummyRemoteParameters, 'controlling')); 261 }, 'start() throws if later called with a different role'); 262 263 test(t => { 264 const iceTransport = makeIceTransport(t); 265 iceTransport.start({ 266 usernameFragment: '1'.repeat(4), 267 password: '1'.repeat(22), 268 }); 269 iceTransport.addRemoteCandidate(candidate1); 270 const changedRemoteParameters = { 271 usernameFragment: '2'.repeat(4), 272 password: '2'.repeat(22), 273 }; 274 iceTransport.start(changedRemoteParameters); 275 assert_equals(iceTransport.state, 'new'); 276 assert_array_equals(iceTransport.getRemoteCandidates(), []); 277 assert_ice_parameters_equals(iceTransport.getRemoteParameters(), 278 changedRemoteParameters); 279 }, `start() flushes remote candidates and transitions state to 'new' if ` + 280 'later called with different remote parameters'); 281 282 promise_test(async t => { 283 const [ localTransport, remoteTransport ] = 284 makeGatherAndStartTwoIceTransports(t); 285 const localWatcher = new EventWatcher(t, localTransport, 'statechange'); 286 const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange'); 287 await Promise.all([ 288 localWatcher.wait_for('statechange').then(() => { 289 assert_equals(localTransport.state, 'connected'); 290 }), 291 remoteWatcher.wait_for('statechange').then(() => { 292 assert_equals(remoteTransport.state, 'connected'); 293 }), 294 ]); 295 }, 'Two RTCIceTransports connect to each other'); 296 297 ['controlling', 'controlled'].forEach(role => { 298 promise_test(async t => { 299 const [ localTransport, remoteTransport ] = 300 makeAndGatherTwoIceTransports(t); 301 localTransport.start(remoteTransport.getLocalParameters(), role); 302 remoteTransport.start(localTransport.getLocalParameters(), role); 303 const localWatcher = new EventWatcher(t, localTransport, 'statechange'); 304 const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange'); 305 await Promise.all([ 306 localWatcher.wait_for('statechange').then(() => { 307 assert_equals(localTransport.state, 'connected'); 308 }), 309 remoteWatcher.wait_for('statechange').then(() => { 310 assert_equals(remoteTransport.state, 'connected'); 311 }), 312 ]); 313 }, `Two RTCIceTransports configured with the ${role} role resolve the ` + 314 'conflict in band and still connect.'); 315 }); 316 317 promise_test(async t => { 318 async function waitForSelectedCandidatePairChangeThenConnected(t, transport, 319 transportName) { 320 const watcher = new EventWatcher(t, transport, 321 [ 'statechange', 'selectedcandidatepairchange' ]); 322 await watcher.wait_for('selectedcandidatepairchange'); 323 const selectedCandidatePair = transport.getSelectedCandidatePair(); 324 assert_not_equals(selectedCandidatePair, null, 325 `${transportName} selected candidate pair should not be null once ` + 326 'the selectedcandidatepairchange event fires'); 327 assert_true( 328 transport.getLocalCandidates().some( 329 ({ candidate }) => 330 candidate === selectedCandidatePair.local.candidate), 331 `${transportName} selected candidate pair local should be in the ` + 332 'list of local candidates'); 333 assert_true( 334 transport.getRemoteCandidates().some( 335 ({ candidate }) => 336 candidate === selectedCandidatePair.remote.candidate), 337 `${transportName} selected candidate pair local should be in the ` + 338 'list of remote candidates'); 339 await watcher.wait_for('statechange'); 340 assert_equals(transport.state, 'connected', 341 `${transportName} state should be 'connected'`); 342 } 343 const [ localTransport, remoteTransport ] = 344 makeGatherAndStartTwoIceTransports(t); 345 await Promise.all([ 346 waitForSelectedCandidatePairChangeThenConnected(t, localTransport, 347 'local transport'), 348 waitForSelectedCandidatePairChangeThenConnected(t, remoteTransport, 349 'remote transport'), 350 ]); 351 }, 'Selected candidate pair changes once the RTCIceTransports connect.'); 352 353 promise_test(async t => { 354 const [ transport, ] = makeGatherAndStartTwoIceTransports(t); 355 const watcher = new EventWatcher(t, transport, 'selectedcandidatepairchange'); 356 await watcher.wait_for('selectedcandidatepairchange'); 357 transport.stop(); 358 assert_equals(transport.getSelectedCandidatePair(), null); 359 }, 'getSelectedCandidatePair() returns null once the RTCIceTransport is ' + 360 'stopped.'); 361 362 </script>