Bug 1377420 - add RID check in RTP in simulcast mochitests
* Added RTP parser
MozReview-Commit-ID: B31iK4cDpOQ
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -9,16 +9,17 @@ support-files =
nonTrickleIce.js
pc.js
templates.js
NetworkPreparationChromeScript.js
blacksilence.js
turnConfig.js
sdpUtils.js
addTurnsSelfsignedCert.js
+ parser_rtp.js
!/dom/canvas/test/captureStream_common.js
!/dom/canvas/test/webgl-mochitest/webgl-util.js
!/dom/media/test/manifest.js
!/dom/media/test/320x240.ogv
!/dom/media/test/r11025_s16_c1.wav
!/dom/media/test/bug461281.ogg
!/dom/media/test/seek.webm
!/dom/media/test/gizmo.mp4
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/parser_rtp.js
@@ -0,0 +1,131 @@
+/* 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/. */
+
+"use strict";
+
+/*
+ * Parses an RTP packet
+ * @param buffer an ArrayBuffer that contains the packet
+ * @return { type: "rtp", header: {...}, payload: a DataView }
+ */
+var ParseRtpPacket = (buffer) => {
+
+ // DataView.getFooInt returns big endian numbers by default
+ let view = new DataView(buffer);
+
+ // Standard Header Fields
+ // https://tools.ietf.org/html/rfc3550#section-5.1
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // |V=2|P|X| CC |M| PT | sequence number |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | timestamp |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | synchronization source (SSRC) identifier |
+ // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ // | contributing source (CSRC) identifiers |
+ // | .... |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ let header = {};
+ let offset = 0;
+ // Note that incrementing the offset happens as close to reading the data as
+ // possible. This simplifies ensuring that the number of read bytes and the
+ // offset increment match. Data may be manipulated between when the offset is
+ // incremented and before the next read.
+ let byte = view.getUint8(offset);
+ offset++;
+ // Version 2 Bit
+ header.version = (0xC0 & byte) >> 6;
+ // Padding 1 Bit
+ header.padding = (0x30 & byte) >> 5
+ // Extension 1 Bit
+ header.extensionsPresent = ((0x10 & byte) >> 4) == 1;
+ // CSRC count 4 Bit
+ header.csrcCount = (0xF & byte);
+
+ byte = view.getUint8(offset);
+ offset++;
+ // Marker 1 Bit
+ header.marker = (0x80 & byte) >> 7;
+ // Payload Type 7 Bit
+ header.payloadType = (0x7F & byte);
+ // Sequence Number 16 Bit
+ header.sequenceNumber = view.getUint16(offset);
+ offset += 2;
+ // Timestamp 32 Bit
+ header.timestamp = view.getUint32(offset);
+ offset += 4;
+ // SSRC 32 Bit
+ header.ssrc = view.getUint32(offset);
+ offset += 4;
+
+ // CSRC 32 Bit
+ header.csrcs = [];
+ for (let c = 0; c < header.csrcCount; c++) {
+ header.csrcs.push(view.getUint32(offset));
+ offset += 4;
+ }
+
+ // Extensions
+ header.extensions = [];
+ header.extensionPaddingBytes = 0;
+ header.extensionsTotalLength = 0;
+ if ( header.extensionsPresent ) {
+ // https://tools.ietf.org/html/rfc3550#section-5.3.1
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | defined by profile | length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | header extension |
+ // | .... |
+ let addExtension = (id, len) => header.extensions.push({
+ id: id,
+ data: new DataView(buffer, offset, len),
+ });
+ let extensionId = view.getUint16(offset);
+ offset += 2;
+ // len is in 32 bit units, not bytes
+ header.extensionsTotalLength = view.getUint16(offset) * 4;
+ offset += 2;
+ // Check for https://tools.ietf.org/html/rfc5285
+ if (extensionId != 0xBEDE) {
+ // No rfc5285
+ addExtension(extensionId, header.extensionsTotalLength);
+ offset += header.extensionsTotalLength;
+ } else {
+ let expectedEnd = offset + header.extensionsTotalLength;
+ while (offset < expectedEnd) {
+ // We only support "one-byte" extension headers ATM
+ // https://tools.ietf.org/html/rfc5285#section-4.2
+ // 0
+ // 0 1 2 3 4 5 6 7
+ // +-+-+-+-+-+-+-+-+
+ // | ID | len |
+ // +-+-+-+-+-+-+-+-+
+ byte = view.getUint8(offset);
+ offset++;
+ // Check for padding which can occur between extensions or at the end
+ if (byte == 0) {
+ header.extensionPaddingBytes++;
+ continue;
+ }
+ let id = (byte & 0xF0) >> 4;
+ // Check for the FORBIDDEN id (15), dun dun dun
+ if (id == 15) {
+ // Ignore bytes until until the end of extensions
+ offset = expectedEnd;
+ break;
+ }
+ // the length of the extention is len + 1
+ let len = (byte & 0x0F) + 1;
+ addExtension(id, len);
+ offset += len;
+ }
+ }
+ }
+ return { type: "rtp", header: header, payload: new DataView(buffer, offset) };
+}
\ No newline at end of file
--- a/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
@@ -1,12 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1231507",
title: "Basic video-only peer connection with Simulcast offer",
@@ -72,30 +73,58 @@
info("Answer with RIDs: " + JSON.stringify(test._remote_answer));
ok(test._remote_answer.sdp.match(/a=simulcast:/), "Modified answer has simulcast");
ok(test._remote_answer.sdp.match(/a=rid:foo/), "Modified answer has rid foo");
ok(test._remote_answer.sdp.match(/a=rid:bar/), "Modified answer has rid bar");
ok(test._remote_answer.sdp.match(/urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id/), "Modified answer has RID");
}
]);
+ // For storing the rid extension so it can be checked in the RTP
+ let ridExtensionId = 0;
+
// do this after set local description so the MediaPipeline
// has been created.
test.chain.insertAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION',[
function PC_REMOTE_SET_RTP_FIRST_RID(test) {
const extmap_id = test.originalOffer.sdp.match(
"a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
ok(extmap_id, "Original offer has extmap id for simulcast: " + extmap_id[1]);
+ ridExtensionId = extmap_id[1];
// Cause pcRemote to filter out everything but RID "foo", only
// allowing one of the simulcast streams through.
addRIDExtension(test.pcRemote, extmap_id[1]);
selectRecvRID(test.pcRemote, "foo");
}
]);
+ let getRtpPacket = (pc) => {
+ pc.mozEnablePacketDump(0, "rtp", false);
+ return new Promise((res, rej) =>
+ pc.mozSetPacketCallback((...args) => {
+ res([...args]);
+ pc.mozSetPacketCallback(() => {});
+ pc.mozDisablePacketDump(0, "rtp", false);
+ })
+ );
+ }
+
+ test.chain.insertBefore('PC_REMOTE_WAIT_FOR_MEDIA_FLOW', [
+ async function PC_REMOTE_CHECK_RID_IN_RTP() {
+ let pc = SpecialPowers.wrap(test.pcRemote._pc);
+ let [level, type, sending, data] = await getRtpPacket(pc);
+ let extensions = ParseRtpPacket(data).header.extensions;
+ ok(ridExtensionId, "RID extension ID has been extracted from SDP");
+ let ridExt = extensions.find(e => e.id == ridExtensionId);
+ ok(ridExt, "RID is present in RTP.");
+ is(new TextDecoder('utf-8').decode(ridExt.data), "foo",
+ "RID is 'foo'.");
+ }
+ ]);
+
test.chain.append([
async function PC_REMOTE_WAIT_FOR_FRAMES() {
const vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
emitter.start();
await helper.checkVideoPlaying(vremote);
emitter.stop();
},