Bug 1377420 - add RID check in RTP in simulcast mochitests draft
authorNico Grunbaum
Fri, 30 Jun 2017 01:02:55 -0700
changeset 693792 7b9d0603834ee2aa6d5e36e15f6b69d05c498323
parent 692227 3502694e2053f9d8a730f8b8ea1c2e783e58dba3
child 739136 10c344600a495d86c49a8efb30deda8e0334123b
push id87916
push userna-g@nostrum.com
push dateMon, 06 Nov 2017 21:20:48 +0000
bugs1377420
milestone58.0a1
Bug 1377420 - add RID check in RTP in simulcast mochitests * Added RTP parser MozReview-Commit-ID: B31iK4cDpOQ
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/parser_rtp.js
dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
--- 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();
         },