Bug 1377299: Add packet dump hooks r+drno draft
authorByron Campen [:bwc] <docfaraday@gmail.com>
Thu, 29 Jun 2017 19:14:06 -0500
changeset 674441 5dc0671ee7eab659fa51e93110069409e146ea8c
parent 674396 9be05b2177667ed8221f9da4fdcc200dbdf3de62
child 734327 ca5b0c8d15eff432f79fc1fd46acdc7a33f87032
push id82833
push userbcampen@mozilla.com
push dateTue, 03 Oct 2017 18:58:33 +0000
bugs1377299
milestone58.0a1
Bug 1377299: Add packet dump hooks r+drno MozReview-Commit-ID: Jr7n49OOduG
dom/media/PeerConnection.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_peerConnection_checkPacketDumpHook.html
dom/webidl/PeerConnectionImpl.webidl
dom/webidl/PeerConnectionObserver.webidl
dom/webidl/RTCPeerConnection.webidl
media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
media/webrtc/signaling/src/peerconnection/PacketDumper.cpp
media/webrtc/signaling/src/peerconnection/PacketDumper.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/moz.build
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1173,16 +1173,28 @@ class RTCPeerConnection {
   mozAddRIDExtension(receiver, extensionId) {
     this._impl.addRIDExtension(receiver.track, extensionId);
   }
 
   mozAddRIDFilter(receiver, rid) {
     this._impl.addRIDFilter(receiver.track, rid);
   }
 
+  mozSetPacketCallback(callback) {
+    this._onPacket = callback;
+  }
+
+  mozEnablePacketDump(level, type, sending) {
+    this._impl.enablePacketDump(level, type, sending);
+  }
+
+  mozDisablePacketDump(level, type, sending) {
+    this._impl.disablePacketDump(level, type, sending);
+  }
+
   get localDescription() {
     this._checkClosed();
     let sdp = this._impl.localDescription;
     if (sdp.length == 0) {
       return null;
     }
     return new this._win.RTCSessionDescription({ type: this._localType, sdp });
   }
@@ -1611,16 +1623,23 @@ class PeerConnectionObserver {
   }
 
   onDTMFToneChange(trackId, tone) {
     var pc = this._dompc;
     var sender = pc._senders.find(({track}) => track.id == trackId);
     sender.dtmf.dispatchEvent(new pc._win.RTCDTMFToneChangeEvent("tonechange",
                                                                  { tone }));
   }
+
+  onPacket(level, type, sending, packet) {
+    var pc = this._dompc;
+    if (pc._onPacket) {
+      pc._onPacket(level, type, sending, packet);
+    }
+  }
 }
 setupPrototype(PeerConnectionObserver, {
   classID: PC_OBS_CID,
   contractID: PC_OBS_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer])
 });
 
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -91,16 +91,18 @@ skip-if = toolkit == 'android' # no scre
 [test_getUserMedia_trackCloneCleanup.html]
 [test_getUserMedia_trackEnded.html]
 [test_getUserMedia_peerIdentity.html]
 [test_peerConnection_addIceCandidate.html]
 [test_peerConnection_addtrack_removetrack_events.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudio.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
+[test_peerConnection_checkPacketDumpHook.html]
+skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTLS.html]
 skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' # websockets don't work on android (bug 1266217)
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_checkPacketDumpHook.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1377299",
+    title: "Check that packet dump hooks generate callbacks"
+  });
+
+  function waitForPacket(pc, checkFunction) {
+    return new Promise(resolve => {
+      function onPacket(level, type, sending, packet) {
+        if (checkFunction(level, type, sending, packet)) {
+          SpecialPowers.wrap(pc).mozSetPacketCallback(() => {});
+          resolve();
+        }
+      }
+
+      SpecialPowers.wrap(pc).mozSetPacketCallback(onPacket);
+    }
+    );
+  }
+
+  async function waitForSendPacket(pc, type, level) {
+    await SpecialPowers.wrap(pc).mozEnablePacketDump(level, type, true);
+    await timeout(
+      waitForPacket(pc, (obsLevel, obsType, sending) => {
+        is(obsLevel, level, "Level for packet is " + level);
+        is(obsType, type, "Type for packet is " + type);
+        ok(sending, "This is a send packet");
+        return true;
+      }),
+      10000, "Timeout waiting for " + type + " send packet on level " + level);
+    await SpecialPowers.wrap(pc).mozDisablePacketDump(level, type, true);
+  }
+
+  async function waitForRecvPacket(pc, type, level) {
+    await SpecialPowers.wrap(pc).mozEnablePacketDump(level, type, false);
+    await timeout(
+      waitForPacket(pc, (obsLevel, obsType, sending) => {
+        is(obsLevel, level, "Level for packet is " + level);
+        is(obsType, type, "Type for packet is " + type);
+        ok(!sending, "This is a recv packet");
+        return true;
+      }),
+      10000, "Timeout waiting for " + type + " recv packet on level " + level);
+    await SpecialPowers.wrap(pc).mozDisablePacketDump(level, type, false);
+  }
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    test.setMediaConstraints([{audio: true, video: true}],
+                             [{audio: true, video: true}]);
+    // pc.js uses video elements by default, we want to test audio elements here
+    test.pcLocal.audioElementsOnly = true;
+
+    test.chain.insertBefore('PC_LOCAL_WAIT_FOR_MEDIA_FLOW',[
+        async function PC_LOCAL_CHECK_PACKET_DUMP_HOOKS() {
+          await waitForRecvPacket(test.pcLocal._pc, "rtp", 0);
+          await waitForRecvPacket(test.pcLocal._pc, "rtcp", 0);
+          await waitForRecvPacket(test.pcLocal._pc, "srtp", 0);
+          await waitForRecvPacket(test.pcLocal._pc, "srtcp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "rtp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "rtcp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "srtp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "srtcp", 0);
+
+          await waitForRecvPacket(test.pcLocal._pc, "rtp", 1);
+          await waitForRecvPacket(test.pcLocal._pc, "rtcp", 1);
+          await waitForRecvPacket(test.pcLocal._pc, "srtp", 1);
+          await waitForRecvPacket(test.pcLocal._pc, "srtcp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "rtp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "rtcp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "srtp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "srtcp", 1);
+        },
+        async function PC_REMOTE_CHECK_PACKET_DUMP_HOOKS() {
+          await waitForRecvPacket(test.pcRemote._pc, "rtp", 0);
+          await waitForRecvPacket(test.pcRemote._pc, "rtcp", 0);
+          await waitForRecvPacket(test.pcRemote._pc, "srtp", 0);
+          await waitForRecvPacket(test.pcRemote._pc, "srtcp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "rtp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "rtcp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "srtp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "srtcp", 0);
+
+          await waitForRecvPacket(test.pcRemote._pc, "rtp", 1);
+          await waitForRecvPacket(test.pcRemote._pc, "rtcp", 1);
+          await waitForRecvPacket(test.pcRemote._pc, "srtp", 1);
+          await waitForRecvPacket(test.pcRemote._pc, "srtcp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "rtp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "rtcp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "srtp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "srtcp", 1);
+        }
+    ]);
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -61,16 +61,24 @@ interface PeerConnectionImpl  {
   void closeStreams();
 
   sequence<MediaStream> getLocalStreams();
   sequence<MediaStream> getRemoteStreams();
 
   void addRIDExtension(MediaStreamTrack recvTrack, unsigned short extensionId);
   void addRIDFilter(MediaStreamTrack recvTrack, DOMString rid);
 
+  void enablePacketDump(unsigned long level,
+                        mozPacketDumpType type,
+                        boolean sending);
+
+  void disablePacketDump(unsigned long level,
+                         mozPacketDumpType type,
+                         boolean sending);
+
   /* As the ICE candidates roll in this one should be called each time
    * in order to keep the candidate list up-to-date for the next SDP-related
    * call PeerConnectionImpl does not parse ICE candidates, just sticks them
    * into the SDP.
    */
   [Throws]
   void addIceCandidate(DOMString candidate, DOMString mid, unsigned short level);
 
--- a/dom/webidl/PeerConnectionObserver.webidl
+++ b/dom/webidl/PeerConnectionObserver.webidl
@@ -42,9 +42,13 @@ interface PeerConnectionObserver
   /* Changes to MediaStreamTracks */
   void onAddStream(MediaStream stream);
   void onRemoveStream(MediaStream stream);
   void onAddTrack(MediaStreamTrack track, sequence<MediaStream> streams);
   void onRemoveTrack(MediaStreamTrack track);
 
   /* DTMF callback */
   void onDTMFToneChange(DOMString trackId, DOMString tone);
+
+  /* Packet dump callback */
+  void onPacket(unsigned long level, mozPacketDumpType type, boolean sending,
+                ArrayBuffer packet);
 };
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -31,16 +31,28 @@ enum RTCIceConnectionState {
     "checking",
     "connected",
     "completed",
     "failed",
     "disconnected",
     "closed"
 };
 
+enum mozPacketDumpType {
+  "rtp", // dump unencrypted rtp as the MediaPipeline sees it
+  "srtp", // dump encrypted rtp as the MediaPipeline sees it
+  "rtcp", // dump unencrypted rtcp as the MediaPipeline sees it
+  "srtcp" // dump encrypted rtcp as the MediaPipeline sees it
+};
+
+callback mozPacketCallback = void (unsigned long level,
+                                   mozPacketDumpType type,
+                                   boolean sending,
+                                   ArrayBuffer packet);
+
 dictionary RTCDataChannelInit {
   boolean        ordered = true;
   unsigned short maxPacketLifeTime;
   unsigned short maxRetransmits;
   DOMString      protocol = "";
   boolean        negotiated = false;
   unsigned short id;
 
@@ -118,16 +130,26 @@ interface RTCPeerConnection : EventTarge
 
   sequence<RTCRtpSender> getSenders();
   sequence<RTCRtpReceiver> getReceivers();
 
   [ChromeOnly]
   void mozAddRIDExtension(RTCRtpReceiver receiver, unsigned short extensionId);
   [ChromeOnly]
   void mozAddRIDFilter(RTCRtpReceiver receiver, DOMString rid);
+  [ChromeOnly]
+  void mozSetPacketCallback(mozPacketCallback callback);
+  [ChromeOnly]
+  void mozEnablePacketDump(unsigned long level,
+                           mozPacketDumpType type,
+                           boolean sending);
+  [ChromeOnly]
+  void mozDisablePacketDump(unsigned long level,
+                            mozPacketDumpType type,
+                            boolean sending);
 
   void close ();
   attribute EventHandler onnegotiationneeded;
   attribute EventHandler onicecandidate;
   attribute EventHandler onsignalingstatechange;
   attribute EventHandler onaddstream; // obsolete
   attribute EventHandler onaddtrack;  // obsolete
   attribute EventHandler ontrack;     // replaces onaddtrack and onaddstream.
--- a/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
@@ -350,17 +350,17 @@ class TestAgentSend : public TestAgent {
         ConfigureSendMediaCodec(&audio_config_);
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
 
     audio_stream_track_ = new FakeAudioStreamTrack();
   }
 
   virtual void CreatePipelines_s(bool aIsRtcpMux) {
 
-    std::string test_pc("PC");
+    std::string test_pc;
 
     if (aIsRtcpMux) {
       ASSERT_FALSE(audio_rtcp_transport_.flow_);
     }
 
     RefPtr<TransportFlow> rtp(audio_rtp_transport_.flow_);
     RefPtr<TransportFlow> rtcp(audio_rtcp_transport_.flow_);
 
@@ -400,17 +400,17 @@ class TestAgentReceive : public TestAgen
 
     mozilla::MediaConduitErrorCode err =
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
         ConfigureRecvMediaCodecs(codecs);
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
   }
 
   virtual void CreatePipelines_s(bool aIsRtcpMux) {
-      std::string test_pc("PC");
+      std::string test_pc;
 
     if (aIsRtcpMux) {
       ASSERT_FALSE(audio_rtcp_transport_.flow_);
     }
 
     audio_pipeline_ = new mozilla::MediaPipelineReceiveAudio(
         test_pc,
         nullptr,
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -594,16 +594,17 @@ MediaPipeline::MediaPipeline(const std::
 
 MediaPipeline::~MediaPipeline() {
   ASSERT_ON_THREAD(main_thread_);
   MOZ_MTLOG(ML_INFO, "Destroying MediaPipeline: " << description_);
 }
 
 nsresult MediaPipeline::Init() {
   ASSERT_ON_THREAD(main_thread_);
+  packet_dumper_ = new PacketDumper(pc_);
 
   if (direction_ == RECEIVE) {
     conduit_->SetReceiverTransport(transport_);
   } else {
     conduit_->SetTransmitterTransport(transport_);
   }
 
   RUN_ON_THREAD(sts_thread_,
@@ -629,16 +630,19 @@ void
 MediaPipeline::DetachTransport_s()
 {
   ASSERT_ON_THREAD(sts_thread_);
 
   disconnect_all();
   transport_->Detach();
   rtp_.Detach();
   rtcp_.Detach();
+
+  // Make sure any cycles are broken
+  packet_dumper_ = nullptr;
 }
 
 nsresult
 MediaPipeline::AttachTransport_s()
 {
   ASSERT_ON_THREAD(sts_thread_);
   nsresult res;
   MOZ_ASSERT(rtp_.transport_);
@@ -686,17 +690,20 @@ MediaPipeline::UpdateTransport_s(int lev
   bool rtcp_mux = false;
   if (!rtcp_transport) {
     rtcp_transport = rtp_transport;
     rtcp_mux = true;
   }
 
   if ((rtp_transport != rtp_.transport_) ||
       (rtcp_transport != rtcp_.transport_)) {
-    DetachTransport_s();
+    disconnect_all();
+    transport_->Detach();
+    rtp_.Detach();
+    rtcp_.Detach();
     rtp_ = TransportInfo(rtp_transport, rtcp_mux ? MUX : RTP);
     rtcp_ = TransportInfo(rtcp_transport, rtcp_mux ? MUX : RTCP);
     AttachTransport_s();
   }
 
   level_ = level;
 
   if (filter_ && filter) {
@@ -1085,16 +1092,19 @@ void MediaPipeline::RtpPacketReceived(Tr
         csrc_stats_.insert(std::make_pair(header.arrOfCSRCs[i],
             RtpCSRCStats(header.arrOfCSRCs[i],now)));
       } else {
         csrcInfo->second.SetTimestamp(now);
       }
     }
   }
 
+  packet_dumper_->Dump(
+      level_, dom::mozPacketDumpType::Srtp, false, data, len);
+
   // Make a copy rather than cast away constness
   auto inner_data = MakeUnique<unsigned char[]>(len);
   memcpy(inner_data.get(), data, len);
   int out_len = 0;
   nsresult res = rtp_.recv_srtp_->UnprotectRtp(inner_data.get(),
                                                len, len, &out_len);
   if (!NS_SUCCEEDED(res)) {
     char tmp[16];
@@ -1110,16 +1120,19 @@ void MediaPipeline::RtpPacketReceived(Tr
     return;
   }
   MOZ_MTLOG(ML_DEBUG, description_ << " received RTP packet.");
   increment_rtp_packets_received(out_len);
 
   RtpLogger::LogPacket(inner_data.get(), out_len, true, true, header.headerLength,
                        description_);
 
+  packet_dumper_->Dump(
+      level_, dom::mozPacketDumpType::Rtp, false, inner_data.get(), out_len);
+
   (void)conduit_->ReceivedRTPPacket(inner_data.get(), out_len, header.ssrc);  // Ignore error codes
 }
 
 void MediaPipeline::RtcpPacketReceived(TransportLayer *layer,
                                        const unsigned char *data,
                                        size_t len) {
   if (!transport_->pipeline()) {
     MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; transport disconnected");
@@ -1155,16 +1168,19 @@ void MediaPipeline::RtcpPacketReceived(T
   // TODO bug 1279153: remove SR check for reduced size RTCP
   if (filter_ && direction_ == RECEIVE) {
     if (!filter_->FilterSenderReport(data, len)) {
       MOZ_MTLOG(ML_NOTICE, "Dropping incoming RTCP packet; filtered out");
       return;
     }
   }
 
+  packet_dumper_->Dump(
+      level_, dom::mozPacketDumpType::Srtcp, false, data, len);
+
   // Make a copy rather than cast away constness
   auto inner_data = MakeUnique<unsigned char[]>(len);
   memcpy(inner_data.get(), data, len);
   int out_len;
 
   nsresult res = rtcp_.recv_srtp_->UnprotectRtcp(inner_data.get(),
                                                  len,
                                                  len,
@@ -1173,16 +1189,19 @@ void MediaPipeline::RtcpPacketReceived(T
   if (!NS_SUCCEEDED(res))
     return;
 
   MOZ_MTLOG(ML_DEBUG, description_ << " received RTCP packet.");
   increment_rtcp_packets_received();
 
   RtpLogger::LogPacket(inner_data.get(), out_len, true, false, 0, description_);
 
+  packet_dumper_->Dump(
+      level_, dom::mozPacketDumpType::Rtcp, false, data, len);
+
   MOZ_ASSERT(rtcp_.recv_srtp_);  // This should never happen
 
   (void)conduit_->ReceivedRTCPPacket(inner_data.get(), out_len);  // Ignore error codes
 }
 
 bool MediaPipeline::IsRtp(const unsigned char *data, size_t len) {
   if (len < 2)
     return false;
@@ -1677,38 +1696,50 @@ nsresult MediaPipeline::PipelineTranspor
     }
     RtpLogger::LogPacket(data->data(), data->len(), false, is_rtp, header_len,
                          pipeline_->description_);
   }
 
   int out_len;
   nsresult res;
   if (is_rtp) {
+    pipeline_->packet_dumper_->Dump(
+        pipeline_->level(), dom::mozPacketDumpType::Rtp, true, data->data(), data->len());
+
     res = transport.send_srtp_->ProtectRtp(data->data(),
                                            data->len(),
                                            data->capacity(),
                                            &out_len);
   } else {
+    pipeline_->packet_dumper_->Dump(
+        pipeline_->level(), dom::mozPacketDumpType::Rtcp, true, data->data(), data->len());
+
     res = transport.send_srtp_->ProtectRtcp(data->data(),
                                             data->len(),
                                             data->capacity(),
                                             &out_len);
   }
   if (!NS_SUCCEEDED(res)) {
     return res;
   }
 
   // paranoia; don't have uninitialized bytes included in data->len()
   data->SetLength(out_len);
 
   MOZ_MTLOG(ML_DEBUG, pipeline_->description_ << " sending " <<
             (is_rtp ? "RTP" : "RTCP") << " packet");
   if (is_rtp) {
+    pipeline_->packet_dumper_->Dump(
+        pipeline_->level(), dom::mozPacketDumpType::Srtp, true, data->data(), out_len);
+
     pipeline_->increment_rtp_packets_sent(out_len);
   } else {
+    pipeline_->packet_dumper_->Dump(
+        pipeline_->level(), dom::mozPacketDumpType::Srtcp, true, data->data(), out_len);
+
     pipeline_->increment_rtcp_packets_sent();
   }
   return pipeline_->SendPacket(transport.transport_, data->data(), out_len);
 }
 
 nsresult MediaPipeline::PipelineTransport::SendRtcpPacket(
     const uint8_t* data, size_t len) {
 
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -16,16 +16,17 @@
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Atomics.h"
 #include "SrtpFlow.h"
 #include "databuffer.h"
 #include "runnable_utils.h"
 #include "transportflow.h"
 #include "AudioPacketizer.h"
 #include "StreamTracks.h"
+#include "signaling/src/peerconnection/PacketDumper.h"
 
 #include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
 
 // Should come from MediaEngine.h, but that's a pain to include here
 // because of the MOZILLA_EXTERNAL_LINKAGE stuff.
 #define WEBRTC_DEFAULT_SAMPLE_RATE 32000
 
 class nsIPrincipal;
@@ -317,16 +318,18 @@ class MediaPipeline : public sigslot::ha
   // Written on Init. Read on STS thread.
   std::string pc_;
   std::string description_;
 
   // Written on Init, all following accesses are on the STS thread.
   nsAutoPtr<MediaPipelineFilter> filter_;
   nsAutoPtr<webrtc::RtpHeaderParser> rtp_parser_;
 
+  nsAutoPtr<PacketDumper> packet_dumper_;
+
  private:
   // Gets the current time as a DOMHighResTimeStamp
   static DOMHighResTimeStamp GetNow();
   nsresult Init_s();
 
   bool IsRtp(const unsigned char *data, size_t len);
 };
 
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PacketDumper.cpp
@@ -0,0 +1,67 @@
+/* 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/. */
+
+#include "signaling/src/peerconnection/PacketDumper.h"
+#include "signaling/src/peerconnection/PeerConnectionImpl.h"
+#include "mozilla/media/MediaUtils.h" // NewRunnableFrom
+#include "nsThreadUtils.h" // NS_DispatchToMainThread
+
+namespace mozilla {
+
+PacketDumper::PacketDumper(PeerConnectionImpl* aPc) :
+  mPc(aPc)
+{
+}
+
+PacketDumper::PacketDumper(const std::string& aPcHandle)
+{
+  if (!aPcHandle.empty()) {
+    PeerConnectionWrapper pcw(aPcHandle);
+    mPc = pcw.impl();
+  }
+}
+
+PacketDumper::~PacketDumper()
+{
+  RefPtr<Runnable> pcDisposeRunnable =
+    media::NewRunnableFrom(
+        std::bind(
+          [](RefPtr<PeerConnectionImpl> pc) {
+            return NS_OK;
+          },
+          mPc.forget()));
+  NS_DispatchToMainThread(pcDisposeRunnable);
+}
+
+void
+PacketDumper::Dump(size_t level, dom::mozPacketDumpType type, bool sending,
+                   const void* data, size_t size)
+{
+  // Optimization; avoids making a copy of the buffer, but we need to lock a
+  // mutex and check the flags. Could be optimized further, if we really want to
+  if (!mPc || !mPc->ShouldDumpPacket(level, type, sending)) {
+    return;
+  }
+
+  RefPtr<PeerConnectionImpl> pc = mPc;
+
+  UniquePtr<uint8_t[]> ownedPacket = MakeUnique<uint8_t[]>(size);
+  memcpy(ownedPacket.get(), data, size);
+
+  RefPtr<Runnable> dumpRunnable =
+    media::NewRunnableFrom(
+        std::bind(
+          [pc, level, type, sending, size](UniquePtr<uint8_t[]>& packet)
+            -> nsresult
+          {
+            pc->DumpPacket_m(level, type, sending, packet, size);
+            return NS_OK;
+          },
+          Move(ownedPacket)));
+
+  NS_DispatchToMainThread(dumpRunnable);
+}
+
+} //namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/PacketDumper.h
@@ -0,0 +1,35 @@
+/* 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/. */
+
+#ifndef _PACKET_DUMPER_H_
+#define _PACKET_DUMPER_H_
+
+#include "nsISupportsImpl.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+
+namespace mozilla {
+class PeerConnectionImpl;
+
+class PacketDumper
+{
+  public:
+    explicit PacketDumper(PeerConnectionImpl* aPc);
+    explicit PacketDumper(const std::string& aPcHandle);
+    PacketDumper(const PacketDumper&) = delete;
+    ~PacketDumper();
+
+    PacketDumper& operator=(const PacketDumper&) = delete;
+
+    void Dump(size_t level, dom::mozPacketDumpType type, bool sending,
+              const void* data, size_t size);
+
+  private:
+    RefPtr<PeerConnectionImpl> mPc;
+};
+
+} // namespace mozilla
+
+#endif // _PACKET_DUMPER_H_
+
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -312,16 +312,18 @@ PeerConnectionImpl::PeerConnectionImpl(c
   , mUuidGen(MakeUnique<PCUuidGenerator>())
   , mHaveConfiguredCodecs(false)
   , mHaveDataStream(false)
   , mAddCandidateErrorCount(0)
   , mTrickle(true) // TODO(ekr@rtfm.com): Use pref
   , mNegotiationNeeded(false)
   , mPrivateWindow(false)
   , mActiveOnWindow(false)
+  , mPacketDumpEnabled(false)
+  , mPacketDumpFlagsMutex("Packet dump flags mutex")
 {
   MOZ_ASSERT(NS_IsMainThread());
   auto log = RLogConnector::CreateInstance();
   if (aGlobal) {
     mWindow = do_QueryInterface(aGlobal->GetAsSupports());
     if (IsPrivateBrowsing(mWindow)) {
       mPrivateWindow = true;
       log->EnterPrivateMode();
@@ -2323,16 +2325,79 @@ PeerConnectionImpl::GetStreamId(const DO
 
 void
 PeerConnectionImpl::OnMediaError(const std::string& aError)
 {
   CSFLogError(logTag, "Encountered media error! %s", aError.c_str());
   // TODO: Let content know about this somehow.
 }
 
+bool
+PeerConnectionImpl::ShouldDumpPacket(size_t level, dom::mozPacketDumpType type,
+                                     bool sending) const
+{
+  if (!mPacketDumpEnabled) {
+    return false;
+  }
+
+  MutexAutoLock lock(mPacketDumpFlagsMutex);
+
+  const std::vector<unsigned>* packetDumpFlags;
+
+  if (sending) {
+    packetDumpFlags = &mSendPacketDumpFlags;
+  } else {
+    packetDumpFlags = &mRecvPacketDumpFlags;
+  }
+
+  if (level < packetDumpFlags->size()) {
+    unsigned flag = 1 << (unsigned)type;
+    return flag & packetDumpFlags->at(level);
+  }
+
+  return false;
+}
+
+void
+PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type,
+                                 bool sending, UniquePtr<uint8_t[]>& packet,
+                                 size_t size)
+{
+  if (IsClosed()) {
+    return;
+  }
+
+  if (!ShouldDumpPacket(level, type, sending)) {
+    return;
+  }
+
+  RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
+  if (!pco) {
+    return;
+  }
+
+  // TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere
+  // else?
+  AutoJSAPI jsapi;
+  if(!jsapi.Init(GetWindow())) {
+    return;
+  }
+
+  JS::Rooted<JSObject*> jsobj(jsapi.cx(),
+    JS_NewArrayBufferWithContents(jsapi.cx(), size, packet.release()));
+
+  RootedSpiderMonkeyInterface<ArrayBuffer> arrayBuffer(jsapi.cx());
+  if (!arrayBuffer.Init(jsobj)) {
+    return;
+  }
+
+  JSErrorResult jrv;
+  pco->OnPacket(level, type, sending, arrayBuffer, jrv);
+}
+
 nsresult
 PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack,
                              const Sequence<OwningNonNull<DOMMediaStream>>& aStreams)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
   if (!aStreams.Length()) {
     CSFLogError(logTag, "%s: At least one stream arg required", __FUNCTION__);
@@ -2417,16 +2482,62 @@ PeerConnectionImpl::AddRIDFilter(MediaSt
 {
   RefPtr<MediaPipeline> pipeline = GetMediaPipelineForTrack(aRecvTrack);
   if (pipeline) {
     pipeline->AddRIDFilter_m(NS_ConvertUTF16toUTF8(aRid).get());
   }
   return NS_OK;
 }
 
+nsresult
+PeerConnectionImpl::EnablePacketDump(unsigned long level,
+                                     dom::mozPacketDumpType type,
+                                     bool sending)
+{
+  mPacketDumpEnabled = true;
+  std::vector<unsigned>* packetDumpFlags;
+  if (sending) {
+    packetDumpFlags = &mSendPacketDumpFlags;
+  } else {
+    packetDumpFlags = &mRecvPacketDumpFlags;
+  }
+
+  unsigned flag = 1 << (unsigned)type;
+
+  MutexAutoLock lock(mPacketDumpFlagsMutex);
+  if (level >= packetDumpFlags->size()) {
+    packetDumpFlags->resize(level + 1);
+  }
+
+  (*packetDumpFlags)[level] |= flag;
+  return NS_OK;
+}
+
+nsresult
+PeerConnectionImpl::DisablePacketDump(unsigned long level,
+                                      dom::mozPacketDumpType type,
+                                      bool sending)
+{
+  std::vector<unsigned>* packetDumpFlags;
+  if (sending) {
+    packetDumpFlags = &mSendPacketDumpFlags;
+  } else {
+    packetDumpFlags = &mRecvPacketDumpFlags;
+  }
+
+  unsigned flag = 1 << (unsigned)type;
+
+  MutexAutoLock lock(mPacketDumpFlagsMutex);
+  if (level < packetDumpFlags->size()) {
+    (*packetDumpFlags)[level] &= ~flag;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) {
   PC_AUTO_ENTER_API_CALL(true);
 
   std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
 
   nsString wideTrackId;
   aTrack.GetId(wideTrackId);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -19,23 +19,25 @@
 #include "IPeerConnection.h"
 #include "sigslot.h"
 #include "nricectx.h"
 #include "nricemediastream.h"
 #include "nsComponentManagerUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsIUUIDGenerator.h"
 #include "nsIThread.h"
+#include "mozilla/Mutex.h"
 
 #include "signaling/src/jsep/JsepSession.h"
 #include "signaling/src/jsep/JsepSessionImpl.h"
 #include "signaling/src/sdp/SdpMediaSection.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/PeerConnectionImplEnumsBinding.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h" // mozPacketDumpType, maybe move?
 #include "PrincipalChangeObserver.h"
 #include "StreamTracks.h"
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/net/DataChannel.h"
 #include "VideoUtils.h"
 #include "VideoSegment.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
@@ -461,16 +463,34 @@ public:
   // test-only: called from simulcast mochitests.
   NS_IMETHODIMP_TO_ERRORRESULT(AddRIDFilter, ErrorResult& rv,
                                dom::MediaStreamTrack& aRecvTrack,
                                const nsAString& aRid)
   {
     rv = AddRIDFilter(aRecvTrack, aRid);
   }
 
+  // test-only
+  NS_IMETHODIMP_TO_ERRORRESULT(EnablePacketDump, ErrorResult& rv,
+                               unsigned long level,
+                               dom::mozPacketDumpType type,
+                               bool sending)
+  {
+    rv = EnablePacketDump(level, type, sending);
+  }
+
+  // test-only
+  NS_IMETHODIMP_TO_ERRORRESULT(DisablePacketDump, ErrorResult& rv,
+                               unsigned long level,
+                               dom::mozPacketDumpType type,
+                               bool sending)
+  {
+    rv = DisablePacketDump(level, type, sending);
+  }
+
   nsresult GetPeerIdentity(nsAString& peerIdentity)
   {
     if (mPeerIdentity) {
       peerIdentity = mPeerIdentity->ToString();
       return NS_OK;
     }
 
     peerIdentity.SetIsVoid(true);
@@ -621,16 +641,22 @@ public:
   // PeerConnectionMedia can't do it because it doesn't know about principals
   virtual void PrincipalChanged(dom::MediaStreamTrack* aTrack) override;
 
   static std::string GetStreamId(const DOMMediaStream& aStream);
   static std::string GetTrackId(const dom::MediaStreamTrack& track);
 
   void OnMediaError(const std::string& aError);
 
+  bool ShouldDumpPacket(size_t level, dom::mozPacketDumpType type,
+                        bool sending) const;
+
+  void DumpPacket_m(size_t level, dom::mozPacketDumpType type, bool sending,
+                    UniquePtr<uint8_t[]>& packet, size_t size);
+
 private:
   virtual ~PeerConnectionImpl();
   PeerConnectionImpl(const PeerConnectionImpl&rhs);
   PeerConnectionImpl& operator=(PeerConnectionImpl);
   nsresult CalculateFingerprint(const std::string& algorithm,
                                 std::vector<uint8_t>* fingerprint) const;
   nsresult ConfigureJsepSessionCodecs();
 
@@ -807,16 +833,21 @@ private:
     uint32_t mInterToneGap;
   };
 
   static void
   DTMFSendTimerCallback_m(nsITimer* timer, void*);
 
   nsTArray<DTMFState> mDTMFStates;
 
+  std::vector<unsigned> mSendPacketDumpFlags;
+  std::vector<unsigned> mRecvPacketDumpFlags;
+  Atomic<bool> mPacketDumpEnabled;
+  mutable Mutex mPacketDumpFlagsMutex;
+
 public:
   //these are temporary until the DataChannel Listen/Connect API is removed
   unsigned short listenPort;
   unsigned short connectPort;
   char *connectStr; // XXX ownership/free
 };
 
 // This is what is returned when you acquire on a handle
--- a/media/webrtc/signaling/src/peerconnection/moz.build
+++ b/media/webrtc/signaling/src/peerconnection/moz.build
@@ -19,15 +19,16 @@ LOCAL_INCLUDES += [
     '/media/webrtc/signaling/src/mediapipeline',
     '/media/webrtc/trunk',
 ]
 
 # Multiple uses of logTag
 SOURCES += [
     'MediaPipelineFactory.cpp',
     'MediaStreamList.cpp',
+    'PacketDumper.cpp',
     'PeerConnectionCtx.cpp',
     'PeerConnectionImpl.cpp',
     'PeerConnectionMedia.cpp',
     'WebrtcGlobalInformation.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'