summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-ice
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc-ice')
-rw-r--r--testing/web-platform/tests/webrtc-ice/META.yml3
-rw-r--r--testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension-helper.js42
-rw-r--r--testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension.https.html362
3 files changed, 407 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc-ice/META.yml b/testing/web-platform/tests/webrtc-ice/META.yml
new file mode 100644
index 0000000000..e683349e3c
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-ice/META.yml
@@ -0,0 +1,3 @@
+spec: https://w3c.github.io/webrtc-ice/
+suggested_reviewers:
+ - alvestrand
diff --git a/testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension-helper.js b/testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension-helper.js
new file mode 100644
index 0000000000..659ec59b8d
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension-helper.js
@@ -0,0 +1,42 @@
+'use strict';
+
+// Construct an RTCIceTransport instance. The instance will automatically be
+// cleaned up when the test finishes.
+function makeIceTransport(t) {
+ const iceTransport = new RTCIceTransport();
+ t.add_cleanup(() => iceTransport.stop());
+ return iceTransport;
+}
+
+// Construct two RTCIceTransport instances, configure them to exchange
+// candidates, then gather() them.
+// Returns a 2-list: [ RTCIceTransport, RTCIceTransport ]
+function makeAndGatherTwoIceTransports(t) {
+ const localTransport = makeIceTransport(t);
+ const remoteTransport = makeIceTransport(t);
+ localTransport.onicecandidate = e => {
+ if (e.candidate) {
+ remoteTransport.addRemoteCandidate(e.candidate);
+ }
+ };
+ remoteTransport.onicecandidate = e => {
+ if (e.candidate) {
+ localTransport.addRemoteCandidate(e.candidate);
+ }
+ };
+ localTransport.gather({});
+ remoteTransport.gather({});
+ return [ localTransport, remoteTransport ];
+}
+
+// Construct two RTCIceTransport instances, configure them to exchange
+// candidates and parameters, then gather() and start() them.
+// Returns a 2-list:
+// [ controlling RTCIceTransport,
+// controlled RTCIceTransport ]
+function makeGatherAndStartTwoIceTransports(t) {
+ const [ localTransport, remoteTransport ] = makeAndGatherTwoIceTransports(t);
+ localTransport.start(remoteTransport.getLocalParameters(), 'controlling');
+ remoteTransport.start(localTransport.getLocalParameters(), 'controlled');
+ return [ localTransport, remoteTransport ];
+}
diff --git a/testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension.https.html b/testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension.https.html
new file mode 100644
index 0000000000..bb4d52adce
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-ice/RTCIceTransport-extension.https.html
@@ -0,0 +1,362 @@
+<!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
+
+const ICE_UFRAG = 'u'.repeat(4);
+const ICE_PWD = 'p'.repeat(22);
+
+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_js(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_dom('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_dom('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: ICE_UFRAG,
+ password: ICE_PWD,
+};
+
+test(() => {
+ const iceTransport = new RTCIceTransport();
+ iceTransport.stop();
+ assert_throws_dom('InvalidStateError',
+ () => iceTransport.start(dummyRemoteParameters));
+ assert_equals(iceTransport.getRemoteParameters(), null);
+}, `start() throws if closed`);
+
+test(() => {
+ const iceTransport = new RTCIceTransport();
+ assert_throws_js(TypeError, () => iceTransport.start({}));
+ assert_throws_js(TypeError,
+ () => iceTransport.start({ usernameFragment: ICE_UFRAG }));
+ assert_throws_js(TypeError,
+ () => iceTransport.start({ password: ICE_PWD }));
+ assert_equals(iceTransport.getRemoteParameters(), null);
+}, 'start() throws if usernameFragment or password not set');
+
+test(() => {
+ const TEST_CASES = [
+ {usernameFragment: '2sh', description: 'less than 4 characters long'},
+ {
+ usernameFragment: 'x'.repeat(257),
+ description: 'greater than 256 characters long',
+ },
+ {usernameFragment: '123\n', description: 'illegal character'},
+ ];
+ for (const {usernameFragment, description} of TEST_CASES) {
+ const iceTransport = new RTCIceTransport();
+ assert_throws_dom(
+ 'SyntaxError',
+ () => iceTransport.start({ usernameFragment, password: ICE_PWD }),
+ `illegal usernameFragment (${description}) should throw a SyntaxError`);
+ }
+}, 'start() throws if usernameFragment does not conform to syntax');
+
+test(() => {
+ const TEST_CASES = [
+ {password: 'x'.repeat(21), description: 'less than 22 characters long'},
+ {
+ password: 'x'.repeat(257),
+ description: 'greater than 256 characters long',
+ },
+ {password: ('x'.repeat(21) + '\n'), description: 'illegal character'},
+ ];
+ for (const {password, description} of TEST_CASES) {
+ const iceTransport = new RTCIceTransport();
+ assert_throws_dom(
+ 'SyntaxError',
+ () => iceTransport.start({ usernameFragment: ICE_UFRAG, password }),
+ `illegal password (${description}) should throw a SyntaxError`);
+ }
+}, 'start() throws if password does not conform to syntax');
+
+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_dom('InvalidStateError',
+ () => iceTransport.addRemoteCandidate(candidate1));
+ assert_array_equals(iceTransport.getRemoteCandidates(), []);
+}, 'addRemoteCandidate() throws if closed');
+
+test(() => {
+ const iceTransport = new RTCIceTransport();
+ assert_throws_dom('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_dom('InvalidStateError',
+ () => iceTransport.start(dummyRemoteParameters, 'controlling'));
+}, 'start() throws if later called with a different role');
+
+test(t => {
+ const iceTransport = makeIceTransport(t);
+ iceTransport.start({
+ usernameFragment: '1'.repeat(4),
+ password: '1'.repeat(22),
+ });
+ iceTransport.addRemoteCandidate(candidate1);
+ const changedRemoteParameters = {
+ usernameFragment: '2'.repeat(4),
+ password: '2'.repeat(22),
+ };
+ 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>