362 lines
14 KiB
HTML
362 lines
14 KiB
HTML
<!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>
|