<html>
<head>
<script type="text/javascript" src="webrtc_test_utilities.js"></script>
<script type="text/javascript" src="webrtc_test_common.js"></script>
<script type="text/javascript">
$ = function(id) {
return document.getElementById(id);
};
var gPeerConnection = null;
var gCertificate = null;
// This test creates and sets three offers, calling setConfiguration in
// between each offer, expecting an ICE restart to be triggered by the next
// offer.
async function testSetConfiguration() {
gPeerConnection = new RTCPeerConnection(
{iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:0});
// Change ICE candidate pool size, which will succeed before
// setLocalDescription is called.
gPeerConnection.setConfiguration(
{iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
// Now test successful cases of setConfiguration. Changes should trigger an
// ICE restart in the next offer. To do this, first we need to trigger an
// initial ICE gathering phase and wait until it completes.
function waitForState(state) {
return new Promise((resolve) => {
gPeerConnection.onicegatheringstatechange = (candidate) => {
if (gPeerConnection.iceGatheringState === state) {
resolve();
}
};
});
}
let onCompletePromise = waitForState('complete');
await createOfferAndSetLocalDescription();
await onCompletePromise;
onCompletePromise = waitForState('complete');
// Policy changed.
gPeerConnection.setConfiguration(
{iceServers:[], iceTransportPolicy:'relay', bundlePolicy:'balanced',
rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
await createOfferAndSetLocalDescription();
await onCompletePromise;
// Only wait for 'gathering', since it will take a while for the requests to
// 'foo.invalid' to time out.
const onGatheringPromise = waitForState('gathering');
// Servers changed.
gPeerConnection.setConfiguration(
{iceServers:[{urls:'stun:foo.invalid'}], iceTransportPolicy:'all',
bundlePolicy:'balanced', rtcpMuxPolicy:'require', certificates:[],
iceCandidatePoolSize:1});
await createOfferAndSetLocalDescription();
await onGatheringPromise;
return logSuccess();
}
async function testSetConfigurationErrors() {
// Generate certificate so we can test the InvalidModificationError from
// attempting to change certificates.
let certificate;
try {
certificate = await RTCPeerConnection.generateCertificate(
{ name:'ECDSA', namedCurve:'P-256' });
} catch {
throw new Error('Failed to generate certificate.');
}
gCertificate = certificate;
return continueTestSetConfigurationErrors();
}
// Continued after certificate generated.
async function continueTestSetConfigurationErrors() {
gPeerConnection = new RTCPeerConnection(
{iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
// If bundlePolicy, rtcpMuxPolicy or certificates are changed, an
// InvalidModificationError should be thrown.
assertThrows(gPeerConnection.setConfiguration,
{iceServers:[], iceTransportPolicy:'all',
bundlePolicy:'max-bundle', rtcpMuxPolicy:'require',
certificates:[], iceCandidatePoolSize:1});
assertThrows(gPeerConnection.setConfiguration,
{iceServers:[], iceTransportPolicy:'all',
bundlePolicy:'balanced', rtcpMuxPolicy:'negotiate',
certificates:[]});
assertThrows(gPeerConnection.setConfiguration,
{iceServers:[], iceTransportPolicy:'all',
bundlePolicy:'balanced', rtcpMuxPolicy:'require',
certificates:[gCertificate], iceCandidatePoolSize:1});
// Failure to parse URL should result in SyntaxError.
assertThrows(gPeerConnection.setConfiguration,
{iceServers:[{url:'stunnnn:foo.invalid'}],
iceTransportPolicy:'all', bundlePolicy:'max-bundle',
rtcpMuxPolicy:'require', certificates:[],
iceCandidatePoolSize:1});
// TURN server with missing username should result in InvalidAccessError.
assertThrows(gPeerConnection.setConfiguration,
{iceServers:[{url:'turn:foo.invalid'}],
iceTransportPolicy:'all', bundlePolicy:'max-bundle',
rtcpMuxPolicy:'require', certificates:[],
iceCandidatePoolSize:1});
// Sanity check that a configuration can be successfully set, and thus
// there's not something unexpected causing the above exceptions.
gPeerConnection.setConfiguration(
{iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
// Lastly: only after applying a local description, changing the candidate
// pool size is not allowed.
let offer;
try {
offer = await gPeerConnection.createOffer({offerToReceiveAudio:1});
} catch {
throw new Error('Failed to generate offer.');
}
console.log("Setting offer:\n" + offer.sdp);
try {
await gPeerConnection.setLocalDescription(offer);
} catch {
throw new Error('Failed to set local description.');
}
// Pool size absent, which means it should default to 0, which is
// different than its current value of 1.
assertThrows(gPeerConnection.setConfiguration,
{iceServers:[], iceTransportPolicy:'all',
bundlePolicy:'balanced', rtcpMuxPolicy:'require',
certificates:[]});
return logSuccess();
}
function assertThrows(func) {
try {
func.apply(arguments.slice(start=1));
} catch (e) {
return;
}
throw new Error('Expected exception to be thrown by: ' + func);
}
// Helper function to create and apply offer.
async function createOfferAndSetLocalDescription() {
let offer;
try {
offer = await gPeerConnection.createOffer({offerToReceiveAudio:1});
} catch {
throw new Error('Failed to generate offer.');
}
console.log("Setting offer:\n" + offer.sdp);
try {
return gPeerConnection.setLocalDescription(offer);
} catch {
throw new Error('Failed to set local description.');
}
}
</script>
</head>
<body>
</body>
</html>