Bug 1253706: Mochitests for setConfiguration. r=jib

Mainly covers ICE servers, since we don't have support for that in wpt.

Differential Revision: https://phabricator.services.mozilla.com/D135362
This commit is contained in:
Byron Campen [:bwc] 2022-02-08 23:37:55 +00:00
Родитель 5467f16d85
Коммит f6d630b181
4 изменённых файлов: 457 добавлений и 23 удалений

Просмотреть файл

@ -20,37 +20,87 @@ const findStatsRelayCandidates = async (pc, protocol) => {
);
};
// Trickles candidates if pcDst is set, and resolves the candidate list
const trickleIce = async (pc, pcDst) => {
const candidates = [],
addCandidatePromises = [];
while (true) {
const { candidate } = await new Promise(r =>
pc.addEventListener("icecandidate", r, { once: true })
);
if (!candidate) {
break;
}
candidates.push(candidate);
if (pcDst) {
addCandidatePromises.push(pcDst.addIceCandidate(candidate));
}
}
await Promise.all(addCandidatePromises);
return candidates;
};
const gather = async pc => {
await pc.setLocalDescription(
await pc.createOffer({ offerToReceiveAudio: true })
);
return new Promise(r => {
const candidates = [];
const onCandidate = e => {
if (e.candidate) {
candidates.push(e.candidate);
} else {
r(candidates);
pc.removeEventListener("icecandidate", onCandidate);
}
};
pc.addEventListener("icecandidate", onCandidate);
});
if (pc.signalingState == "stable") {
await pc.setLocalDescription(
await pc.createOffer({ offerToReceiveAudio: true })
);
} else if (pc.signalingState == "have-remote-offer") {
await pc.setLocalDescription();
}
return trickleIce(pc);
};
const gatherWithTimeout = async (pc, timeout, context) => {
const throwOnTimeout = async () => {
await wait(timeout);
throw `Gathering did not complete within ${timeout} ms with ${context}`;
throw new Error(
`Gathering did not complete within ${timeout} ms with ${context}`
);
};
let result = [];
try {
result = await Promise.race([gather(pc), throwOnTimeout()]);
} catch (e) {
ok(false, e);
}
return result;
return Promise.race([gather(pc), throwOnTimeout()]);
};
const iceConnected = async pc => {
return new Promise((resolve, reject) => {
pc.addEventListener("iceconnectionstatechange", () => {
if (["connected", "completed"].includes(pc.iceConnectionState)) {
resolve();
} else if (pc.iceConnectionState == "failed") {
reject(new Error(`ICE failed`));
}
});
});
};
const connect = async (offerer, answerer) => {
const trickle1 = trickleIce(offerer, answerer);
const trickle2 = trickleIce(answerer, offerer);
const offer = await offerer.createOffer({ offerToReceiveAudio: true });
await offerer.setLocalDescription(offer);
await answerer.setRemoteDescription(offer);
const answer = await answerer.createAnswer();
await Promise.all([
trickle1,
trickle2,
offerer.setRemoteDescription(answer),
answerer.setLocalDescription(answer),
iceConnected(offerer),
iceConnected(answerer),
]);
};
const connectWithTimeout = async (offerer, answerer, timeout, context) => {
const throwOnTimeout = async () => {
await wait(timeout);
throw new Error(
`ICE did not complete within ${timeout} ms with ${context}`
);
};
return Promise.race([connect(offerer, answerer), throwOnTimeout()]);
};
const isV6HostCandidate = candidate => {

Просмотреть файл

@ -146,6 +146,10 @@ skip-if = toolkit == 'android' # no screenshare or windowshare on android
[test_peerConnection_audioSynchronizationSourcesUnidirectional.html]
[test_peerConnection_audioContributingSources.html]
[test_peerConnection_checkPacketDumpHook.html]
[test_peerConnection_gatherWithSetConfiguration.html]
skip-if =
toolkit == 'android' # websockets don't work on android (bug 1266217)
scheme=http
[test_peerConnection_gatherWithStun300.html]
skip-if =
toolkit == 'android' # websockets don't work on android (bug 1266217)

Просмотреть файл

@ -81,6 +81,7 @@ runNetworkTest(() => {
bundlePolicy: "max-bundle",
iceTransportPolicy: "relay",
peerIdentity: null,
certificates: [],
iceServers: [
{ urls: ["stun:127.0.0.1", "stun:localhost"], credentialType:"password" },
{ urls: ["turn:[::1]:3478"], username:"p", credential:"p", credentialType:"password" },

Просмотреть файл

@ -0,0 +1,379 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="iceTestUtils.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script></head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1253706",
title: "Test ICE gathering when setConfiguration is used to change the ICE config"
});
const tests = [
async function baselineV4Cases() {
await checkSrflx([{urls:[`stun:${turnAddressV4}`]}]);
await checkRelayUdp([{urls:[`turn:${turnAddressV4}`], username, credential}]);
await checkRelayTcp([{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]);
await checkRelayUdpTcp([{urls:[`turn:${turnAddressV4}`, `turn:${turnAddressV4}?transport=tcp`], username, credential}]);
await checkNoSrflx();
await checkNoRelay();
},
async function addStunServerBeforeOffer() {
const pc = new RTCPeerConnection();
pc.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidates = await gatherWithTimeout(pc, 32000, `just a stun server`);
ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function addTurnServerBeforeOffer() {
const pc = new RTCPeerConnection();
pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
const candidates = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
pc.close();
},
async function addTurnTcpServerBeforeOffer() {
const pc = new RTCPeerConnection();
pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
const candidates = await gatherWithTimeout(pc, 32000, `a turn (tcp) server`);
ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
pc.close();
},
async function addStunServerAfterOffer() {
const pc = new RTCPeerConnection();
const candidates1 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
await pc.setLocalDescription({type: "rollback"});
pc.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidates2 = await gatherWithTimeout(pc, 32000, `just a stun server`);
ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function addTurnServerAfterOffer() {
const pc = new RTCPeerConnection();
const candidates1 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
await pc.setLocalDescription({type: "rollback"});
pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
const candidates2 = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
pc.close();
},
async function addTurnTcpServerAfterOffer() {
const pc = new RTCPeerConnection();
const candidates1 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
await pc.setLocalDescription({type: "rollback"});
pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
const candidates2 = await gatherWithTimeout(pc, 32000, `a turn (tcp) server`);
ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should get no srflx candidates");
ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
pc.close();
},
async function removeStunServerBeforeOffer() {
const pc = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
pc.setConfiguration({});
const candidates = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function removeTurnServerBeforeOffer() {
const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
pc.setConfiguration({});
const candidates = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function removeTurnTcpServerBeforeOffer() {
const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
pc.setConfiguration({});
const candidates = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function removeStunServerAfterOffer() {
const pc = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidates1 = await gatherWithTimeout(pc, 32000, `just a stun server`);
ok(candidates1.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
await pc.setLocalDescription({type: "rollback"});
pc.setConfiguration({});
const candidates2 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function removeTurnServerAfterOffer() {
const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
const candidates1 = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
ok(candidates1.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(candidates1.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
await pc.setLocalDescription({type: "rollback"});
pc.setConfiguration({});
const candidates2 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function removeTurnTcpServerAfterOffer() {
const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
const candidates1 = await gatherWithTimeout(pc, 32000, `a turn (tcp) server`);
ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should get no srflx candidates");
ok(candidates1.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
await pc.setLocalDescription({type: "rollback"});
pc.setConfiguration({});
const candidates2 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
pc.close();
},
async function addStunServerAfterNegotiation() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidatePromise = trickleIce(offerer);
await connectWithTimeout(offerer, answerer, 32000, `no ICE servers`);
const candidates = await candidatePromise;
const ufrags = Array.from(new Set(candidates.map(c => c.usernameFragment)));
is(ufrags.length, 1, "Should have one ufrag in candidate set");
offerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidates2 = await gatherWithTimeout(offerer, 32000, `just a stun server`);
ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
const ufrags2 = Array.from(new Set(candidates2.map(c => c.usernameFragment)));
is(ufrags2.length, 1, "Should have one ufrag in candidate set");
isnot(ufrags[0], ufrags2[0], "ufrag should change, because setConfiguration should have triggered an ICE restart");
offerer.close();
answerer.close();
},
async function addTurnServerAfterNegotiation() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidatePromise = trickleIce(offerer);
await connectWithTimeout(offerer, answerer, 32000, `no ICE servers`);
const candidates = await candidatePromise;
const ufrags = Array.from(new Set(candidates.map(c => c.usernameFragment)));
is(ufrags.length, 1, "Should have one ufrag in candidate set");
offerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
const candidates2 = await gatherWithTimeout(offerer, 32000, `a turn (udp) server`);
ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
const ufrags2 = Array.from(new Set(candidates2.map(c => c.usernameFragment)));
is(ufrags2.length, 1, "Should have one ufrag in candidate set");
isnot(ufrags[0], ufrags2[0], "ufrag should change, because setConfiguration should have triggered an ICE restart");
offerer.close();
answerer.close();
},
async function addTurnTcpServerAfterNegotiation() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidatePromise = trickleIce(offerer);
await connectWithTimeout(offerer, answerer, 32000, `no ICE servers`);
const candidates = await candidatePromise;
const ufrags = Array.from(new Set(candidates.map(c => c.usernameFragment)));
is(ufrags.length, 1, "Should have one ufrag in candidate set");
offerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
const candidates2 = await gatherWithTimeout(offerer, 32000, `a turn (tcp) server`);
ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
const ufrags2 = Array.from(new Set(candidates2.map(c => c.usernameFragment)));
is(ufrags2.length, 1, "Should have one ufrag in candidate set");
isnot(ufrags[0], ufrags2[0], "ufrag should change, because setConfiguration should have triggered an ICE restart");
offerer.close();
answerer.close();
},
async function addStunServerBeforeCreateAnswer() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
await answerer.setRemoteDescription(await offerer.createOffer({offerToReceiveAudio: true}));
answerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const candidates = await gatherWithTimeout(answerer, 32000, `just a stun server`);
ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
offerer.close();
answerer.close();
},
async function addTurnServerBeforeCreateAnswer() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
await answerer.setRemoteDescription(await offerer.createOffer({offerToReceiveAudio: true}));
answerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
const candidates = await gatherWithTimeout(answerer, 32000, `a turn (udp) server`);
ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
offerer.close();
answerer.close();
},
async function addTurnTcpServerBeforeCreateAnswer() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
await answerer.setRemoteDescription(await offerer.createOffer({offerToReceiveAudio: true}));
answerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
const candidates = await gatherWithTimeout(answerer, 32000, `a turn (tcp) server`);
ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
offerer.close();
answerer.close();
},
async function relayPolicyPreventsSrflx() {
const pc = new RTCPeerConnection();
pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}], iceTransportPolicy: "relay"});
const candidates = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get a srflx candidate");
ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
pc.close();
},
async function addOffererStunServerAllowsIceToConnect() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
try {
// Both ends are behind a simulated endpoint-independent NAT, which
// requires at least one side to have a srflx candidate to work.
await connectWithTimeout(offerer, answerer, 2000, `no ICE servers`);
ok(false, "ICE should either have failed, or timed out!");
} catch (e) {
if (!(e instanceof Error)) throw e;
ok(true, "ICE should either have failed, or timed out!");
}
offerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
await connectWithTimeout(offerer, answerer, 32000, `just a STUN server`);
offerer.close();
answerer.close();
},
async function addAnswererStunServerDoesNotAllowIceToConnect() {
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
try {
// Both ends are behind a simulated endpoint-independent NAT, which
// requires at least one side to have a srflx candidate to work.
await connectWithTimeout(offerer, answerer, 2000, `no ICE servers`);
ok(false, "ICE should either have failed, or timed out!");
} catch (e) {
if (!(e instanceof Error)) throw e;
ok(true, "ICE should either have failed, or timed out!");
}
// This _won't_ help, because the answerer does not get to decide to
// trigger an ICE restart.
answerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
try {
await connectWithTimeout(offerer, answerer, 2000, `no ICE servers`);
ok(false, "ICE should either have failed, or timed out!");
} catch (e) {
if (!(e instanceof Error)) throw e;
ok(true, "ICE should either have failed, or timed out!");
}
offerer.close();
answerer.close();
},
async function addOffererTurnServerAllowsIceToConnect() {
await pushPrefs(
['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT']);
const offerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
try {
// Both ends are behind a simulated port-dependent NAT, which
// requires at least one side to have a relay candidate to work.
await connectWithTimeout(offerer, answerer, 2000, `just a STUN server`);
ok(false, "ICE should either have failed, or timed out!");
} catch (e) {
if (!(e instanceof Error)) throw e;
ok(true, "ICE should either have failed, or timed out!");
}
offerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
await connectWithTimeout(offerer, answerer, 32000, `a TURN (udp) server`);
offerer.close();
answerer.close();
await SpecialPowers.popPrefEnv();
},
];
runNetworkTest(async () => {
const turnServer = iceServersArray.find(server => "username" in server);
username = turnServer.username;
credential = turnServer.credential;
// Just use the first url. It might make sense to look for TURNS first,
// since that will always use a hostname, but on CI we don't have TURNS
// support anyway (see bug 1323439).
const turnHostname = getTurnHostname(turnServer.urls[0]);
turnAddressV4 = await dnsLookupV4(turnHostname);
await pushPrefs(
['media.peerconnection.ice.obfuscate_host_addresses', false],
['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
['media.peerconnection.ice.loopback', true],
['media.getusermedia.insecure.enabled', true]);
for (const test of tests) {
info(`Running test: ${test.name}`);
await test();
info(`Done running test: ${test.name}`);
}
await SpecialPowers.popPrefEnv();
}, { useIceServer: true });
</script>
</pre>
</body>
</html>