Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /webrtc/RTCDtlsTransport-handshake-errors.https.html - WPT Dashboard Interop Dashboard
<!doctype html>
<meta charset=utf-8>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webrtc/RTCPeerConnection-helper.js"></script>
<script>
"use strict";
async function withPrefs(prefs, func) {
await SpecialPowers.pushPrefEnv({ set: prefs });
try {
return await func();
} finally {
await SpecialPowers.popPrefEnv();
}
}
// We use prefs to force DTLS handshake failures, which is not possible in wpt.
promise_test(async t => {
let offerer;
// Make offerer use only DTLS 1.2
await withPrefs([["media.peerconnection.dtls.version.max", 771]], async () => {
offerer = new RTCPeerConnection();
t.add_cleanup(() => offerer.close());
const stream = await getNoiseStream({ audio: true });
offerer.addTrack(stream.getTracks()[0]);
await offerer.setLocalDescription();
// Pref is checked in the transport code, so give that time to happen; init
// should be done if we've seen a trickle candidate.
await new Promise(r => offerer.onicecandidate = r);
});
// Make answerer use only DTLS 1.3
await withPrefs([["media.peerconnection.dtls.version.min", 772]], async () => {
const answerer = new RTCPeerConnection();
t.add_cleanup(() => answerer.close());
exchangeIceCandidates(offerer, answerer);
await answerer.setRemoteDescription(offerer.localDescription);
await answerer.setLocalDescription();
await offerer.setRemoteDescription(answerer.localDescription);
const answererTransport = answerer.getSenders()[0].transport;
const answererErrorPromise = new Promise(r => {
answererTransport.onerror = r;
});
const offererTransport = offerer.getSenders()[0].transport;
const offererErrorPromise = new Promise(r => {
offererTransport.onerror = r;
});
const answererError = await answererErrorPromise;
assert_true(answererError instanceof RTCErrorEvent);
assert_true(answererError.error instanceof RTCError);
assert_equals(answererError.error.errorDetail, "dtls-failure");
// Here we expect a protocol_version alert (70)
// answerer is the DTLS client, so it is the offerer that will alert
assert_equals(answererError.error.sentAlert, null);
assert_equals(answererError.error.receivedAlert, 70);
assert_not_equals(answererError.error.message.length, 0);
assert_equals(answererError.error.name, 'OperationError');
const offererError = await offererErrorPromise;
assert_true(offererError instanceof RTCErrorEvent);
assert_true(offererError.error instanceof RTCError);
assert_equals(offererError.error.errorDetail, "dtls-failure");
assert_equals(offererError.error.sentAlert, 70);
assert_equals(offererError.error.receivedAlert, null);
assert_not_equals(offererError.error.message.length, 0);
assert_equals(offererError.error.name, 'OperationError');
});
}, 'DTLS version mismatch results in the correct error events');
// Like exchangeIceCandidates, but rewrites the address of every candidate to
// one in TEST-NET-1 (RFC 5737), so that every connectivity check fails.
function exchangeUnreachableIceCandidates(pc1, pc2) {
function doExchange(localPc, remotePc) {
localPc.addEventListener("icecandidate", event => {
let { candidate } = event;
if (remotePc.signalingState === "closed") {
return;
}
if (candidate?.candidate) {
// candidate:<foundation> <component> <transport> <priority> <address> ...
const parts = candidate.candidate.split(" ");
parts[4] = "192.0.2.1";
candidate = {
candidate: parts.join(" "),
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex,
usernameFragment: candidate.usernameFragment,
};
}
remotePc.addIceCandidate(candidate);
});
}
doExchange(pc1, pc2);
doExchange(pc2, pc1);
}
function failOnDtlsError(t, pc, name) {
const transport = pc.getTransceivers()[0].sender.transport;
transport.onerror = t.step_func(e => {
assert_unreached(
`${name} fired error on RTCDtlsTransport: ${e.error.errorDetail}`);
});
return transport;
}
promise_test(async t => {
const offerer = new RTCPeerConnection();
t.add_cleanup(() => offerer.close());
const answerer = new RTCPeerConnection();
t.add_cleanup(() => answerer.close());
exchangeUnreachableIceCandidates(offerer, answerer);
offerer.addTransceiver("audio");
await offerer.setLocalDescription();
const offererTransport = failOnDtlsError(t, offerer, "offerer");
await answerer.setRemoteDescription(offerer.localDescription);
await answerer.setLocalDescription();
const answererTransport = failOnDtlsError(t, answerer, "answerer");
await offerer.setRemoteDescription(answerer.localDescription);
// The error event is fired before the statechange event, so once the DTLS
// transports are failed any error event would already have failed the test.
await Promise.all([
waitForState(offererTransport.iceTransport, "failed"),
waitForState(answererTransport.iceTransport, "failed"),
waitForState(offererTransport, "failed"),
waitForState(answererTransport, "failed"),
]);
}, 'ICE failure does not fire an error event on RTCDtlsTransport');
</script>