| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCIceTransport-extensions.https.html</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="RTCIceTransport-extension-helper.js"></script> |
| <script> |
| 'use strict'; |
| |
| // These tests are based on the following extension specification: |
| // https://w3c.github.io/webrtc-ice/ |
| |
| // The following helper functions are called from |
| // RTCIceTransport-extension-helper.js: |
| // makeIceTransport |
| // makeGatherAndStartTwoIceTransports |
| |
| test(() => { |
| const iceTransport = new RTCIceTransport(); |
| }, 'RTCIceTransport constructor does not throw'); |
| |
| test(() => { |
| const iceTransport = new RTCIceTransport(); |
| assert_equals(iceTransport.role, null, 'Expect role to be null'); |
| assert_equals(iceTransport.state, 'new', `Expect state to be 'new'`); |
| assert_equals(iceTransport.gatheringState, 'new', |
| `Expect gatheringState to be 'new'`); |
| assert_array_equals(iceTransport.getLocalCandidates(), [], |
| 'Expect no local candidates'); |
| assert_array_equals(iceTransport.getRemoteCandidates(), [], |
| 'Expect no remote candidates'); |
| assert_equals(iceTransport.getSelectedCandidatePair(), null, |
| 'Expect no selected candidate pair'); |
| assert_not_equals(iceTransport.getLocalParameters(), null, |
| 'Expect local parameters generated'); |
| assert_equals(iceTransport.getRemoteParameters(), null, |
| 'Expect no remote parameters'); |
| }, 'RTCIceTransport initial properties are set'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| assert_throws(new TypeError(), () => |
| iceTransport.gather({ iceServers: null })); |
| }, 'gather() with { iceServers: null } should throw TypeError'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.gather({ iceServers: undefined }); |
| }, 'gather() with { iceServers: undefined } should succeed'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.gather({ iceServers: [{ |
| urls: ['turns:turn.example.org', 'turn:turn.example.net'], |
| username: 'user', |
| credential: 'cred', |
| }] }); |
| }, 'gather() with one turns server, one turn server, username, credential' + |
| ' should succeed'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.gather({ iceServers: [{ |
| urls: ['stun:stun1.example.net', 'stun:stun2.example.net'], |
| }] }); |
| }, 'gather() with 2 stun servers should succeed'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.stop(); |
| assert_throws('InvalidStateError', () => iceTransport.gather({})); |
| }, 'gather() throws if closed'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.gather({}); |
| assert_equals(iceTransport.gatheringState, 'gathering'); |
| }, `gather() transitions gatheringState to 'gathering'`); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.gather({}); |
| assert_throws('InvalidStateError', () => iceTransport.gather({})); |
| }, 'gather() throws if called twice'); |
| |
| promise_test(async t => { |
| const iceTransport = makeIceTransport(t); |
| const watcher = new EventWatcher(t, iceTransport, 'gatheringstatechange'); |
| iceTransport.gather({}); |
| await watcher.wait_for('gatheringstatechange'); |
| assert_equals(iceTransport.gatheringState, 'complete'); |
| }, `eventually transition gatheringState to 'complete'`); |
| |
| promise_test(async t => { |
| const iceTransport = makeIceTransport(t); |
| const watcher = new EventWatcher(t, iceTransport, |
| [ 'icecandidate', 'gatheringstatechange' ]); |
| iceTransport.gather({}); |
| let candidate; |
| do { |
| ({ candidate } = await watcher.wait_for('icecandidate')); |
| } while (candidate !== null); |
| assert_equals(iceTransport.gatheringState, 'gathering'); |
| await watcher.wait_for('gatheringstatechange'); |
| assert_equals(iceTransport.gatheringState, 'complete'); |
| }, 'onicecandidate fires with null candidate before gatheringState' + |
| ` transitions to 'complete'`); |
| |
| promise_test(async t => { |
| const iceTransport = makeIceTransport(t); |
| const watcher = new EventWatcher(t, iceTransport, 'icecandidate'); |
| iceTransport.gather({}); |
| const { candidate } = await watcher.wait_for('icecandidate'); |
| assert_not_equals(candidate.candidate, ''); |
| assert_array_equals(iceTransport.getLocalCandidates(), [candidate]); |
| }, 'gather() returns at least one host candidate'); |
| |
| promise_test(async t => { |
| const iceTransport = makeIceTransport(t); |
| const watcher = new EventWatcher(t, iceTransport, 'icecandidate'); |
| iceTransport.gather({ gatherPolicy: 'relay' }); |
| const { candidate } = await watcher.wait_for('icecandidate'); |
| assert_equals(candidate, null); |
| assert_array_equals(iceTransport.getLocalCandidates(), []); |
| }, `gather() returns no candidates with { gatherPolicy: 'relay'} and no turn` + |
| ' servers'); |
| |
| const dummyRemoteParameters = { |
| usernameFragment: 'dummyUsernameFragment', |
| password: 'dummyPassword', |
| }; |
| |
| test(() => { |
| const iceTransport = new RTCIceTransport(); |
| iceTransport.stop(); |
| assert_throws('InvalidStateError', |
| () => iceTransport.start(dummyRemoteParameters)); |
| assert_equals(iceTransport.getRemoteParameters(), null); |
| }, `start() throws if closed`); |
| |
| test(() => { |
| const iceTransport = new RTCIceTransport(); |
| assert_throws(new TypeError(), () => iceTransport.start({})); |
| assert_throws(new TypeError(), |
| () => iceTransport.start({ usernameFragment: 'dummy' })); |
| assert_throws(new TypeError(), |
| () => iceTransport.start({ password: 'dummy' })); |
| assert_equals(iceTransport.getRemoteParameters(), null); |
| }, 'start() throws if usernameFragment or password not set'); |
| |
| const assert_ice_parameters_equals = (a, b) => { |
| assert_equals(a.usernameFragment, b.usernameFragment, |
| 'usernameFragments are equal'); |
| assert_equals(a.password, b.password, 'passwords are equal'); |
| }; |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.start(dummyRemoteParameters); |
| assert_equals(iceTransport.state, 'new'); |
| assert_ice_parameters_equals(iceTransport.getRemoteParameters(), |
| dummyRemoteParameters); |
| }, `start() does not transition state to 'checking' if no remote candidates ` + |
| 'added'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.start(dummyRemoteParameters); |
| assert_equals(iceTransport.role, 'controlled'); |
| }, `start() with default role sets role attribute to 'controlled'`); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.start(dummyRemoteParameters, 'controlling'); |
| assert_equals(iceTransport.role, 'controlling'); |
| }, `start() sets role attribute to 'controlling'`); |
| |
| const candidate1 = new RTCIceCandidate({ |
| candidate: 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host', |
| sdpMid: '', |
| }); |
| |
| test(() => { |
| const iceTransport = new RTCIceTransport(); |
| iceTransport.stop(); |
| assert_throws('InvalidStateError', |
| () => iceTransport.addRemoteCandidate(candidate1)); |
| assert_array_equals(iceTransport.getRemoteCandidates(), []); |
| }, 'addRemoteCandidate() throws if closed'); |
| |
| test(() => { |
| const iceTransport = new RTCIceTransport(); |
| assert_throws('OperationError', |
| () => iceTransport.addRemoteCandidate( |
| new RTCIceCandidate({ candidate: 'invalid', sdpMid: '' }))); |
| assert_array_equals(iceTransport.getRemoteCandidates(), []); |
| }, 'addRemoteCandidate() throws on invalid candidate'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.addRemoteCandidate(candidate1); |
| iceTransport.start(dummyRemoteParameters); |
| assert_equals(iceTransport.state, 'checking'); |
| assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]); |
| }, `start() transitions state to 'checking' if one remote candidate had been ` + |
| 'added'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.start(dummyRemoteParameters); |
| iceTransport.addRemoteCandidate(candidate1); |
| assert_equals(iceTransport.state, 'checking'); |
| assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]); |
| }, `addRemoteCandidate() transitions state to 'checking' if start() had been ` + |
| 'called before'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.start(dummyRemoteParameters); |
| assert_throws('InvalidStateError', |
| () => iceTransport.start(dummyRemoteParameters, 'controlling')); |
| }, 'start() throws if later called with a different role'); |
| |
| test(t => { |
| const iceTransport = makeIceTransport(t); |
| iceTransport.start({ |
| usernameFragment: 'user', |
| password: 'pass', |
| }); |
| iceTransport.addRemoteCandidate(candidate1); |
| const changedRemoteParameters = { |
| usernameFragment: 'user2', |
| password: 'pass', |
| }; |
| iceTransport.start(changedRemoteParameters); |
| assert_equals(iceTransport.state, 'new'); |
| assert_array_equals(iceTransport.getRemoteCandidates(), []); |
| assert_ice_parameters_equals(iceTransport.getRemoteParameters(), |
| changedRemoteParameters); |
| }, `start() flushes remote candidates and transitions state to 'new' if ` + |
| 'later called with different remote parameters'); |
| |
| promise_test(async t => { |
| const [ localTransport, remoteTransport ] = |
| makeGatherAndStartTwoIceTransports(t); |
| const localWatcher = new EventWatcher(t, localTransport, 'statechange'); |
| const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange'); |
| await Promise.all([ |
| localWatcher.wait_for('statechange').then(() => { |
| assert_equals(localTransport.state, 'connected'); |
| }), |
| remoteWatcher.wait_for('statechange').then(() => { |
| assert_equals(remoteTransport.state, 'connected'); |
| }), |
| ]); |
| }, 'Two RTCIceTransports connect to each other'); |
| |
| ['controlling', 'controlled'].forEach(role => { |
| promise_test(async t => { |
| const [ localTransport, remoteTransport ] = |
| makeAndGatherTwoIceTransports(t); |
| localTransport.start(remoteTransport.getLocalParameters(), role); |
| remoteTransport.start(localTransport.getLocalParameters(), role); |
| const localWatcher = new EventWatcher(t, localTransport, 'statechange'); |
| const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange'); |
| await Promise.all([ |
| localWatcher.wait_for('statechange').then(() => { |
| assert_equals(localTransport.state, 'connected'); |
| }), |
| remoteWatcher.wait_for('statechange').then(() => { |
| assert_equals(remoteTransport.state, 'connected'); |
| }), |
| ]); |
| }, `Two RTCIceTransports configured with the ${role} role resolve the ` + |
| 'conflict in band and still connect.'); |
| }); |
| |
| promise_test(async t => { |
| async function waitForSelectedCandidatePairChangeThenConnected(t, transport, |
| transportName) { |
| const watcher = new EventWatcher(t, transport, |
| [ 'statechange', 'selectedcandidatepairchange' ]); |
| await watcher.wait_for('selectedcandidatepairchange'); |
| const selectedCandidatePair = transport.getSelectedCandidatePair(); |
| assert_not_equals(selectedCandidatePair, null, |
| `${transportName} selected candidate pair should not be null once ` + |
| 'the selectedcandidatepairchange event fires'); |
| assert_true( |
| transport.getLocalCandidates().some( |
| ({ candidate }) => |
| candidate === selectedCandidatePair.local.candidate), |
| `${transportName} selected candidate pair local should be in the ` + |
| 'list of local candidates'); |
| assert_true( |
| transport.getRemoteCandidates().some( |
| ({ candidate }) => |
| candidate === selectedCandidatePair.remote.candidate), |
| `${transportName} selected candidate pair local should be in the ` + |
| 'list of remote candidates'); |
| await watcher.wait_for('statechange'); |
| assert_equals(transport.state, 'connected', |
| `${transportName} state should be 'connected'`); |
| } |
| const [ localTransport, remoteTransport ] = |
| makeGatherAndStartTwoIceTransports(t); |
| await Promise.all([ |
| waitForSelectedCandidatePairChangeThenConnected(t, localTransport, |
| 'local transport'), |
| waitForSelectedCandidatePairChangeThenConnected(t, remoteTransport, |
| 'remote transport'), |
| ]); |
| }, 'Selected candidate pair changes once the RTCIceTransports connect.'); |
| |
| promise_test(async t => { |
| const [ transport, ] = makeGatherAndStartTwoIceTransports(t); |
| const watcher = new EventWatcher(t, transport, 'selectedcandidatepairchange'); |
| await watcher.wait_for('selectedcandidatepairchange'); |
| transport.stop(); |
| assert_equals(transport.getSelectedCandidatePair(), null); |
| }, 'getSelectedCandidatePair() returns null once the RTCIceTransport is ' + |
| 'stopped.'); |
| |
| </script> |