summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/tests/mochitests/sdpUtils.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/webrtc/tests/mochitests/sdpUtils.js398
1 files changed, 398 insertions, 0 deletions
diff --git a/dom/media/webrtc/tests/mochitests/sdpUtils.js b/dom/media/webrtc/tests/mochitests/sdpUtils.js
new file mode 100644
index 0000000000..51cae10dba
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/sdpUtils.js
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var sdputils = {
+ // Finds the codec id / payload type given a codec format
+ // (e.g., "VP8", "VP9/90000"). `offset` tells us which one to use in case of
+ // multiple matches.
+ findCodecId(sdp, format, offset = 0) {
+ let regex = new RegExp("rtpmap:([0-9]+) " + format, "gi");
+ let match;
+ for (let i = 0; i <= offset; ++i) {
+ match = regex.exec(sdp);
+ if (!match) {
+ throw new Error(
+ "Couldn't find offset " +
+ i +
+ " of codec " +
+ format +
+ " while looking for offset " +
+ offset +
+ " in sdp:\n" +
+ sdp
+ );
+ }
+ }
+ // match[0] is the full matched string
+ // match[1] is the first parenthesis group
+ return match[1];
+ },
+
+ // Returns a list of all payload types, excluding rtx, in an sdp.
+ getPayloadTypes(sdp) {
+ const regex = /^a=rtpmap:([0-9]+) (?:(?!rtx).)*$/gim;
+ const pts = [];
+ for (const [line, pt] of sdp.matchAll(regex)) {
+ pts.push(pt);
+ }
+ return pts;
+ },
+
+ // Finds all the extmap ids in the given sdp. Note that this does NOT
+ // consider m-sections, so a more generic version would need to
+ // look at each m-section separately.
+ findExtmapIds(sdp) {
+ var sdpExtmapIds = [];
+ extmapRegEx = /^a=extmap:([0-9+])/gm;
+ // must call exec on the regex to get each match in the string
+ while ((searchResults = extmapRegEx.exec(sdp)) !== null) {
+ // returned array has the matched text as the first item,
+ // and then one item for each capturing parenthesis that
+ // matched containing the text that was captured.
+ sdpExtmapIds.push(searchResults[1]);
+ }
+ return sdpExtmapIds;
+ },
+
+ findExtmapIdsUrnsDirections(sdp) {
+ var sdpExtmap = [];
+ extmapRegEx = /^a=extmap:([0-9+])([A-Za-z/]*) ([A-Za-z0-9_:\-\/\.]+)/gm;
+ // must call exec on the regex to get each match in the string
+ while ((searchResults = extmapRegEx.exec(sdp)) !== null) {
+ // returned array has the matched text as the first item,
+ // and then one item for each capturing parenthesis that
+ // matched containing the text that was captured.
+ var idUrn = [];
+ idUrn.push(searchResults[1]);
+ idUrn.push(searchResults[3]);
+ idUrn.push(searchResults[2].slice(1));
+ sdpExtmap.push(idUrn);
+ }
+ return sdpExtmap;
+ },
+
+ verify_unique_extmap_ids(sdp) {
+ const sdpExtmapIds = sdputils.findExtmapIdsUrnsDirections(sdp);
+
+ return sdpExtmapIds.reduce(function (result, item, index) {
+ const [id, urn, dir] = item;
+ ok(
+ !(id in result) || (result[id][0] === urn && result[id][1] === dir),
+ "ID " + id + " is unique ID for " + urn + " and direction " + dir
+ );
+ result[id] = [urn, dir];
+ return result;
+ }, {});
+ },
+
+ getMSections(sdp) {
+ return sdp
+ .split(new RegExp("^m=", "gm"))
+ .slice(1)
+ .map(s => "m=" + s);
+ },
+
+ getAudioMSections(sdp) {
+ return this.getMSections(sdp).filter(section =>
+ section.startsWith("m=audio")
+ );
+ },
+
+ getVideoMSections(sdp) {
+ return this.getMSections(sdp).filter(section =>
+ section.startsWith("m=video")
+ );
+ },
+
+ checkSdpAfterEndOfTrickle(description, testOptions, label) {
+ info("EOC-SDP: " + JSON.stringify(description));
+
+ const checkForTransportAttributes = msection => {
+ info("Checking msection: " + msection);
+ ok(
+ msection.includes("a=end-of-candidates"),
+ label + ": SDP contains end-of-candidates"
+ );
+
+ if (!msection.startsWith("m=application")) {
+ if (testOptions.rtcpmux) {
+ ok(
+ msection.includes("a=rtcp-mux"),
+ label + ": SDP contains rtcp-mux"
+ );
+ } else {
+ ok(msection.includes("a=rtcp:"), label + ": SDP contains rtcp port");
+ }
+ }
+ };
+
+ const hasOwnTransport = msection => {
+ const port0Check = new RegExp(/^m=\S+ 0 /).exec(msection);
+ if (port0Check) {
+ return false;
+ }
+ const midMatch = new RegExp(/\r\na=mid:(\S+)/).exec(msection);
+ if (!midMatch) {
+ return true;
+ }
+ const mid = midMatch[1];
+ const bundleGroupMatch = new RegExp(
+ "\\r\\na=group:BUNDLE \\S.* " + mid + "\\s+"
+ ).exec(description.sdp);
+ return bundleGroupMatch == null;
+ };
+
+ const msectionsWithOwnTransports = this.getMSections(
+ description.sdp
+ ).filter(hasOwnTransport);
+
+ ok(
+ msectionsWithOwnTransports.length,
+ "SDP should contain at least one msection with a transport"
+ );
+ msectionsWithOwnTransports.forEach(checkForTransportAttributes);
+
+ if (testOptions.ssrc) {
+ ok(description.sdp.includes("a=ssrc"), label + ": SDP contains a=ssrc");
+ } else {
+ ok(
+ !description.sdp.includes("a=ssrc"),
+ label + ": SDP does not contain a=ssrc"
+ );
+ }
+ },
+
+ // Note, we don't bother removing the fmtp lines, which makes a good test
+ // for some SDP parsing issues.
+ removeCodec(sdp, codec) {
+ var updated_sdp = sdp.replace(
+ new RegExp("a=rtpmap:" + codec + ".*\\/90000\\r\\n", ""),
+ ""
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("(RTP\\/SAVPF.*)( " + codec + ")(.*\\r\\n)", ""),
+ "$1$3"
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("a=rtcp-fb:" + codec + " nack\\r\\n", ""),
+ ""
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("a=rtcp-fb:" + codec + " nack pli\\r\\n", ""),
+ ""
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("a=rtcp-fb:" + codec + " ccm fir\\r\\n", ""),
+ ""
+ );
+ return updated_sdp;
+ },
+
+ removeAllButPayloadType(sdp, pt) {
+ return sdp.replace(
+ new RegExp("m=(\\w+ \\w+) UDP/TLS/RTP/SAVPF .*" + pt + ".*\\r\\n", "gi"),
+ "m=$1 UDP/TLS/RTP/SAVPF " + pt + "\r\n"
+ );
+ },
+
+ removeRtpMapForPayloadType(sdp, pt) {
+ return sdp.replace(new RegExp("a=rtpmap:" + pt + ".*\\r\\n", "gi"), "");
+ },
+
+ removeRtcpMux(sdp) {
+ return sdp.replace(/a=rtcp-mux\r\n/g, "");
+ },
+
+ removeSSRCs(sdp) {
+ return sdp.replace(/a=ssrc.*\r\n/g, "");
+ },
+
+ removeBundle(sdp) {
+ return sdp.replace(/a=group:BUNDLE .*\r\n/g, "");
+ },
+
+ reduceAudioMLineToPcmuPcma(sdp) {
+ return sdp.replace(
+ /m=audio .*\r\n/g,
+ "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
+ );
+ },
+
+ setAllMsectionsInactive(sdp) {
+ return sdp
+ .replace(/\r\na=sendrecv/g, "\r\na=inactive")
+ .replace(/\r\na=sendonly/g, "\r\na=inactive")
+ .replace(/\r\na=recvonly/g, "\r\na=inactive");
+ },
+
+ removeAllRtpMaps(sdp) {
+ return sdp.replace(/a=rtpmap:.*\r\n/g, "");
+ },
+
+ reduceAudioMLineToDynamicPtAndOpus(sdp) {
+ return sdp.replace(
+ /m=audio .*\r\n/g,
+ "m=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n"
+ );
+ },
+
+ addTiasBps(sdp, bps) {
+ return sdp.replace(/c=IN (.*)\r\n/g, "c=IN $1\r\nb=TIAS:" + bps + "\r\n");
+ },
+
+ removeSimulcastProperties(sdp) {
+ return sdp
+ .replace(/a=simulcast:.*\r\n/g, "")
+ .replace(/a=rid:.*\r\n/g, "")
+ .replace(
+ /a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id.*\r\n/g,
+ ""
+ )
+ .replace(
+ /a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id.*\r\n/g,
+ ""
+ );
+ },
+
+ transferSimulcastProperties(offer_sdp, answer_sdp) {
+ if (!offer_sdp.includes("a=simulcast:")) {
+ return answer_sdp;
+ }
+ ok(
+ offer_sdp.includes("a=simulcast:send "),
+ "Offer contains simulcast attribute"
+ );
+ var o_simul = offer_sdp.match(/simulcast:send (.*)([\n$])*/i);
+ var new_answer_sdp = answer_sdp + "a=simulcast:recv " + o_simul[1] + "\r\n";
+ ok(offer_sdp.includes("a=rid:"), "Offer contains RID attribute");
+ var o_rids = offer_sdp.match(/a=rid:(.*)/gi);
+ o_rids.forEach(o_rid => {
+ new_answer_sdp = new_answer_sdp + o_rid.replace(/send/, "recv") + "\r\n";
+ });
+ var extmap_id = offer_sdp.match(
+ "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"
+ );
+ ok(extmap_id != null, "Offer contains RID RTP header extension");
+ new_answer_sdp =
+ new_answer_sdp +
+ "a=extmap:" +
+ extmap_id[1] +
+ "/recvonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n";
+ var extmap_id = offer_sdp.match(
+ "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"
+ );
+ if (extmap_id != null) {
+ new_answer_sdp =
+ new_answer_sdp +
+ "a=extmap:" +
+ extmap_id[1] +
+ "/recvonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n";
+ }
+
+ return new_answer_sdp;
+ },
+
+ verifySdp(
+ desc,
+ expectedType,
+ offerConstraintsList,
+ offerOptions,
+ testOptions
+ ) {
+ info("Examining this SessionDescription: " + JSON.stringify(desc));
+ info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
+ info("offerOptions: " + JSON.stringify(offerOptions));
+ ok(desc, "SessionDescription is not null");
+ is(desc.type, expectedType, "SessionDescription type is " + expectedType);
+ ok(desc.sdp.length > 10, "SessionDescription body length is plausible");
+ ok(desc.sdp.includes("a=ice-ufrag"), "ICE username is present in SDP");
+ ok(desc.sdp.includes("a=ice-pwd"), "ICE password is present in SDP");
+ ok(desc.sdp.includes("a=fingerprint"), "ICE fingerprint is present in SDP");
+ //TODO: update this for loopback support bug 1027350
+ ok(
+ !desc.sdp.includes(LOOPBACK_ADDR),
+ "loopback interface is absent from SDP"
+ );
+ var requiresTrickleIce = !desc.sdp.includes("a=candidate");
+ if (requiresTrickleIce) {
+ info("No ICE candidate in SDP -> requiring trickle ICE");
+ } else {
+ info("at least one ICE candidate is present in SDP");
+ }
+
+ //TODO: how can we check for absence/presence of m=application?
+
+ var audioTracks =
+ sdputils.countTracksInConstraint("audio", offerConstraintsList) ||
+ (offerOptions && offerOptions.offerToReceiveAudio ? 1 : 0);
+
+ info("expected audio tracks: " + audioTracks);
+ if (audioTracks == 0) {
+ ok(!desc.sdp.includes("m=audio"), "audio m-line is absent from SDP");
+ } else {
+ ok(desc.sdp.includes("m=audio"), "audio m-line is present in SDP");
+ is(
+ testOptions.opus,
+ desc.sdp.includes("a=rtpmap:109 opus/48000/2"),
+ "OPUS codec is present in SDP"
+ );
+ //TODO: ideally the rtcp-mux should be for the m=audio, and not just
+ // anywhere in the SDP (JS SDP parser bug 1045429)
+ is(
+ testOptions.rtcpmux,
+ desc.sdp.includes("a=rtcp-mux"),
+ "RTCP Mux is offered in SDP"
+ );
+ }
+
+ var videoTracks =
+ sdputils.countTracksInConstraint("video", offerConstraintsList) ||
+ (offerOptions && offerOptions.offerToReceiveVideo ? 1 : 0);
+
+ info("expected video tracks: " + videoTracks);
+ if (videoTracks == 0) {
+ ok(!desc.sdp.includes("m=video"), "video m-line is absent from SDP");
+ } else {
+ ok(desc.sdp.includes("m=video"), "video m-line is present in SDP");
+ if (testOptions.h264) {
+ ok(
+ desc.sdp.includes("a=rtpmap:126 H264/90000") ||
+ desc.sdp.includes("a=rtpmap:97 H264/90000"),
+ "H.264 codec is present in SDP"
+ );
+ } else {
+ ok(
+ desc.sdp.includes("a=rtpmap:120 VP8/90000") ||
+ desc.sdp.includes("a=rtpmap:121 VP9/90000"),
+ "VP8 or VP9 codec is present in SDP"
+ );
+ }
+ is(
+ testOptions.rtcpmux,
+ desc.sdp.includes("a=rtcp-mux"),
+ "RTCP Mux is offered in SDP"
+ );
+ is(
+ testOptions.ssrc,
+ desc.sdp.includes("a=ssrc"),
+ "a=ssrc signaled in SDP"
+ );
+ }
+
+ return requiresTrickleIce;
+ },
+
+ /**
+ * Counts the amount of audio tracks in a given media constraint.
+ *
+ * @param constraints
+ * The contraint to be examined.
+ */
+ countTracksInConstraint(type, constraints) {
+ if (!Array.isArray(constraints)) {
+ return 0;
+ }
+ return constraints.reduce((sum, c) => sum + (c[type] ? 1 : 0), 0);
+ },
+};