<!DOCTYPE HTML> <html> <head> <script type="application/javascript" src="pc.js"></script> <script type="application/javascript" src="iceTestUtils.js"></script> </head> <body> <pre id="test"> <script type="application/javascript"> createHTML({ bug: "1799932", title: "RTCPeerConnection check renegotiation of extmap" }); function setExtmap(sdp, uri, id) { const regex = new RegExp(`a=extmap:[0-9]+(\/[a-z]+)? ${uri}`, 'g'); if (id) { return sdp.replaceAll(regex, `a=extmap:${id}$1 ${uri}`); } else { return sdp.replaceAll(regex, `a=unknownattr`); } } function getExtmap(sdp, uri) { const regex = new RegExp(`a=extmap:([0-9]+)(\/[a-z]+)? ${uri}`); return sdp.match(regex)[1]; } function replaceExtUri(sdp, oldUri, newUri) { const regex = new RegExp(`(a=extmap:[0-9]+\/[a-z]+)? ${oldUri}`, 'g'); return sdp.replaceAll(regex, `$1 ${newUri}`); } const tests = [ async function checkAudioMidChange() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); // Sadly, there's no way to tell the offerer to change the extmap. Other // types of endpoint could conceivably do this, so we at least don't want // to crash. // TODO: Would be nice to be able to test this with an endpoint that // actually changes the ids it uses. const reoffer = await pc1.createOffer(); reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14); info(`New reoffer: ${reoffer.sdp}`); await pc2.setRemoteDescription(reoffer); await pc2.setLocalDescription(); await wait(2000); }, async function checkVideoMidChange() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({video: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); // Sadly, there's no way to tell the offerer to change the extmap. Other // types of endpoint could conceivably do this, so we at least don't want // to crash. // TODO: Would be nice to be able to test this with an endpoint that // actually changes the ids it uses. const reoffer = await pc1.createOffer(); reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14); info(`New reoffer: ${reoffer.sdp}`); await pc2.setRemoteDescription(reoffer); await pc2.setLocalDescription(); await wait(2000); }, async function checkAudioMidSwap() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); // Sadly, there's no way to tell the offerer to change the extmap. Other // types of endpoint could conceivably do this, so we at least don't want // to crash. // TODO: Would be nice to be able to test this with an endpoint that // actually changes the ids it uses. const reoffer = await pc1.createOffer(); const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid"); const ssrcLevelId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", ssrcLevelId); reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", midId); info(`New reoffer: ${reoffer.sdp}`); try { await pc2.setRemoteDescription(reoffer); ok(false, "sRD should fail when it attempts extension id remapping"); } catch (e) { ok(true, "sRD should fail when it attempts extension id remapping"); } }, async function checkVideoMidSwap() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({video: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); // Sadly, there's no way to tell the offerer to change the extmap. Other // types of endpoint could conceivably do this, so we at least don't want // to crash. // TODO: Would be nice to be able to test this with an endpoint that // actually changes the ids it uses. const reoffer = await pc1.createOffer(); const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid"); const toffsetId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset"); reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", toffsetId); reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", midId); info(`New reoffer: ${reoffer.sdp}`); try { await pc2.setRemoteDescription(reoffer); ok(false, "sRD should fail when it attempts extension id remapping"); } catch (e) { ok(true, "sRD should fail when it attempts extension id remapping"); } }, async function checkAudioIdReuse() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); // Sadly, there's no way to tell the offerer to change the extmap. Other // types of endpoint could conceivably do this, so we at least don't want // to crash. // TODO: Would be nice to be able to test this with an endpoint that // actually changes the ids it uses. const reoffer = await pc1.createOffer(); // Change uri, but not the id, so the id now refers to foo. reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo"); info(`New reoffer: ${reoffer.sdp}`); try { await pc2.setRemoteDescription(reoffer); ok(false, "sRD should fail when it attempts extension id remapping"); } catch (e) { ok(true, "sRD should fail when it attempts extension id remapping"); } }, async function checkVideoIdReuse() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({video: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); // Sadly, there's no way to tell the offerer to change the extmap. Other // types of endpoint could conceivably do this, so we at least don't want // to crash. // TODO: Would be nice to be able to test this with an endpoint that // actually changes the ids it uses. const reoffer = await pc1.createOffer(); // Change uri, but not the id, so the id now refers to foo. reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", "foo"); info(`New reoffer: ${reoffer.sdp}`); try { await pc2.setRemoteDescription(reoffer); ok(false, "sRD should fail when it attempts extension id remapping"); } catch (e) { ok(true, "sRD should fail when it attempts extension id remapping"); } }, // What happens when remote answer uses an extmap id, and then a remote // reoffer tries to use the same id for something else? async function checkAudioIdReuseOffererThenAnswerer() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); await connect(pc1, pc2, 32000, "Initial connection"); const reoffer = await pc2.createOffer(); // Change uri, but not the id, so the id now refers to foo. reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo"); info(`New reoffer: ${reoffer.sdp}`); try { await pc1.setRemoteDescription(reoffer); ok(false, "sRD should fail when it attempts extension id remapping"); } catch (e) { ok(true, "sRD should fail when it attempts extension id remapping"); } }, // What happens when a remote offer uses a different extmap id than the // default? Does the answerer remember the new id in reoffers? async function checkAudioIdReuseOffererThenAnswerer() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); // Negotiate, but change id for ssrc-audio-level to something pc2 would // not typically use. await pc1.setLocalDescription(); const mungedOffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 12); await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"}); await pc2.setLocalDescription(); const reoffer = await pc2.createOffer(); is(getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"), "12"); }, async function checkAudioUnnegotiatedIdReuse1() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); // Negotiate, but remove ssrc-audio-level from answer await pc1.setLocalDescription(); const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); await pc2.setRemoteDescription(pc1.localDescription); await pc2.setLocalDescription(); const answerNoExt = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); await pc1.setRemoteDescription({sdp: answerNoExt, type: "answer"}); // Renegotiate, and use the id that offerer used for ssrc-audio-level for // something different (while making sure we don't use it twice) await pc2.setLocalDescription(); const mungedReoffer = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId); const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); await pc1.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"}); }, async function checkAudioUnnegotiatedIdReuse2() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); // Negotiate, but remove ssrc-audio-level from offer. pc2 has never seen // |levelId| in extmap yet, but internally probably wants to use that for // ssrc-audio-level await pc1.setLocalDescription(); const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); const offerNoExt = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); await pc2.setRemoteDescription({sdp: offerNoExt, type: "offer"}); await pc2.setLocalDescription(); await pc1.setRemoteDescription(pc2.localDescription); // Renegotiate, but use |levelId| for something other than // ssrc-audio-level. pc2 should not throw. await pc1.setLocalDescription(); const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId); const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"}); }, async function checkAudioUnnegotiatedIdReuse3() { const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); pc1.addTrack(stream.getTracks()[0]); pc2.addTrack(stream.getTracks()[0]); // Negotiate, but replace ssrc-audio-level with something pc2 won't // support in offer. await pc1.setLocalDescription(); const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); const mungedOffer = replaceExtUri(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "fooba"); await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"}); await pc2.setLocalDescription(); await pc1.setRemoteDescription(pc2.localDescription); // Renegotiate, and use levelId for something pc2 _will_ support. await pc1.setLocalDescription(); const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId); const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined); await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"}); }, ]; runNetworkTest(async () => { for (const test of tests) { info(`Running test: ${test.name}`); await test(); info(`Done running test: ${test.name}`); } }); </script> </pre> </body> </html>