[tel][qmi-snoop] CLI for snoop QMI messages

Change-Id: I724d6853abaf97c483f5ed276f811d35b961c9c0
diff --git a/src/connectivity/telephony/BUILD.gn b/src/connectivity/telephony/BUILD.gn
index 07a01ff..930fcd3 100644
--- a/src/connectivity/telephony/BUILD.gn
+++ b/src/connectivity/telephony/BUILD.gn
@@ -8,6 +8,7 @@
     "//src/connectivity/telephony/config",
     "//src/connectivity/telephony/ril-qmi",
     "//src/connectivity/telephony/telephony",
+    "//src/connectivity/telephony/tools/qmi-snoop",
     "//src/connectivity/telephony/tools/ril-ctl",
   ]
 }
diff --git a/src/connectivity/telephony/drivers/qmi-usb-transport/BUILD.gn b/src/connectivity/telephony/drivers/qmi-usb-transport/BUILD.gn
index 0c9e91f..48ed430 100644
--- a/src/connectivity/telephony/drivers/qmi-usb-transport/BUILD.gn
+++ b/src/connectivity/telephony/drivers/qmi-usb-transport/BUILD.gn
@@ -31,13 +31,14 @@
     "//garnet/public/lib/fsl",
     "//zircon/public/banjo/ddk.protocol.ethernet",
     "//zircon/public/banjo/ddk.protocol.usb",
+    "//zircon/public/fidl/fuchsia-hardware-telephony-transport:fuchsia-hardware-telephony-transport_c",
+    "//zircon/public/fidl/fuchsia-telephony-snoop:fuchsia-telephony-snoop_c",
     "//zircon/public/lib/ddk",
     "//zircon/public/lib/driver",
     "//zircon/public/lib/fit",
     "//zircon/public/lib/sync",
     "//zircon/public/lib/usb",
     "//zircon/public/lib/zx",
-    "//zircon/public/fidl/fuchsia-hardware-telephony-transport:fuchsia-hardware-telephony-transport_c",
   ]
 
   configs += [ "//build/config/fuchsia:enable_zircon_asserts" ]
diff --git a/src/connectivity/telephony/drivers/qmi-usb-transport/qmi-usb-transport.c b/src/connectivity/telephony/drivers/qmi-usb-transport/qmi-usb-transport.c
index e012f56..120989e 100644
--- a/src/connectivity/telephony/drivers/qmi-usb-transport/qmi-usb-transport.c
+++ b/src/connectivity/telephony/drivers/qmi-usb-transport/qmi-usb-transport.c
@@ -12,6 +12,7 @@
 #include <usb/usb.h>
 
 #include <fuchsia/hardware/telephony/transport/c/fidl.h>
+#include <fuchsia/telephony/snoop/c/fidl.h>
 
 #include <zircon/device/qmi-transport.h>
 #include <zircon/hw/usb/cdc.h>
@@ -59,6 +60,10 @@
   zx_handle_t channel_port;
   zx_handle_t channel;
 
+  // Port for snoop QMI messages
+  zx_handle_t snoop_channel_port;
+  zx_handle_t snoop_channel;
+
   usb_protocol_t usb;
   zx_device_t* usb_device;
   zx_device_t* zxdev;
@@ -242,6 +247,46 @@
   return status;
 }
 
+static zx_status_t set_snoop_channel(qmi_ctx_t* qmi_ctx, zx_handle_t channel) {
+  zx_status_t result = ZX_OK;
+  zx_port_packet_t packet;
+  zx_status_t status;
+  // Initialize a port to watch whether the other handle of snoop channel has closed
+  if (qmi_ctx->snoop_channel_port == ZX_HANDLE_INVALID) {
+    status = zx_port_create(0, &qmi_ctx->snoop_channel_port);
+    if (status != ZX_OK) {
+        zxlogf(ERROR,
+                "qmi-usb-transport: failed to create a port to watch snoop channel: "
+                "%s\n",
+                zx_status_get_string(status));
+        return status;
+    }
+  } else {
+    status = zx_port_wait(qmi_ctx->snoop_channel_port, 0, &packet);
+    if (status == ZX_ERR_TIMED_OUT) {
+      zxlogf(ERROR, "qmi-usb-transport: timed out: %s\n",
+              zx_status_get_string(status));
+    } else if (packet.signal.observed & ZX_CHANNEL_PEER_CLOSED) {
+      zxlogf(INFO, "qmi-usb-transport: snoop channel peer closed\n");
+      qmi_ctx->snoop_channel = ZX_HANDLE_INVALID;
+    }
+  }
+
+  if (qmi_ctx->snoop_channel != ZX_HANDLE_INVALID) {
+    zxlogf(ERROR, "snoop channel already connected\n");
+    result = ZX_ERR_ALREADY_BOUND;
+  } else if (channel == ZX_HANDLE_INVALID) {
+    zxlogf(ERROR, "get invalid snoop channel handle\n");
+    result = ZX_ERR_BAD_HANDLE;
+  } else {
+    qmi_ctx->snoop_channel = channel;
+    zx_object_wait_async(
+      qmi_ctx->snoop_channel, qmi_ctx->snoop_channel_port, 0,
+      ZX_CHANNEL_PEER_CLOSED, ZX_WAIT_ASYNC_ONCE);
+  }
+  return result;
+}
+
 #define REPLY(x) fuchsia_hardware_telephony_transport_Qmi##x##_reply
 
 static zx_status_t fidl_SetChannel(void* ctx, zx_handle_t transport, fidl_txn_t* txn) {
@@ -275,11 +320,27 @@
   qmi_update_online_status(qmi_ctx, connected);
   return REPLY(SetNetwork)(txn);
 }
+
+static zx_status_t fidl_SetSnoopChannel(void* ctx, zx_handle_t snoop_channel, fidl_txn_t* txn) {
+  qmi_ctx_t* qmi_ctx = ctx;
+  zx_status_t set_snoop_channel_res;
+  fuchsia_hardware_telephony_transport_Qmi_SetSnoopChannel_Result res;
+  set_snoop_channel_res = set_snoop_channel(qmi_ctx, snoop_channel);
+  if (set_snoop_channel_res == ZX_OK) {
+    res.tag = 0;
+    res.response._reserved = 0;
+  } else {
+    res.tag = 1;
+    res.err = set_snoop_channel_res;
+  }
+  return REPLY(SetSnoopChannel)(txn, &res);
+}
 #undef REPLY
 
 static fuchsia_hardware_telephony_transport_Qmi_ops_t fidl_ops = {
   .SetChannel = fidl_SetChannel,
   .SetNetwork = fidl_SetNetworkStatus,
+  .SetSnoopChannel = fidl_SetSnoopChannel,
 };
 
 static zx_status_t qmi_message(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
@@ -439,6 +500,23 @@
                "qmi-usb-transport: failed to write message to channel: %s\n",
                zx_status_get_string(status));
       }
+      if (qmi_ctx->snoop_channel) {
+        fuchsia_telephony_snoop_Message snoop_msg;
+        snoop_msg.tag = 0;
+        uint32_t current_length;
+        if (sizeof(buffer) > sizeof(snoop_msg.qmi_message.opaque_bytes)) {
+          current_length = sizeof(snoop_msg.qmi_message.opaque_bytes);
+          snoop_msg.qmi_message.is_partial_copy = true;
+        } else {
+          current_length = sizeof(buffer);
+          snoop_msg.qmi_message.is_partial_copy = false;
+        }
+        snoop_msg.qmi_message.direction = fuchsia_telephony_snoop_Direction_FROM_MODEM;
+        snoop_msg.qmi_message.timestamp = zx_clock_get_monotonic();
+        memcpy(snoop_msg.qmi_message.opaque_bytes, buffer, current_length);
+        fuchsia_telephony_snoop_PublisherSendMessage(
+          qmi_ctx->snoop_channel, &snoop_msg);
+      }
       return;
     default:
       zxlogf(ERROR,
@@ -508,6 +586,23 @@
         if (status != ZX_OK) {
           return status;
         }
+        if (ctx->snoop_channel) {
+          fuchsia_telephony_snoop_Message snoop_msg;
+          snoop_msg.tag = 0;
+          uint32_t current_length;
+          if (sizeof(buffer) > sizeof(snoop_msg.qmi_message.opaque_bytes)) {
+            current_length = sizeof(snoop_msg.qmi_message.opaque_bytes);
+            snoop_msg.qmi_message.is_partial_copy = true;
+          } else {
+            current_length = sizeof(buffer);
+            snoop_msg.qmi_message.is_partial_copy = false;
+          }
+          snoop_msg.qmi_message.direction = fuchsia_telephony_snoop_Direction_TO_MODEM;
+          snoop_msg.qmi_message.timestamp = zx_clock_get_monotonic();
+          memcpy(snoop_msg.qmi_message.opaque_bytes, buffer, current_length);
+          fuchsia_telephony_snoop_PublisherSendMessage(
+            ctx->snoop_channel, &snoop_msg);
+        }
       } else if (packet.key == INTERRUPT_MSG) {
         if (txn->response.status == ZX_OK) {
           qmi_handle_interrupt(ctx, txn);
diff --git a/src/connectivity/telephony/lib/qmi/BUILD.gn b/src/connectivity/telephony/lib/qmi/BUILD.gn
index a2738380..2579cc7 100644
--- a/src/connectivity/telephony/lib/qmi/BUILD.gn
+++ b/src/connectivity/telephony/lib/qmi/BUILD.gn
@@ -8,10 +8,12 @@
 rustc_library("qmi") {
   edition = "2018"
   deps = [
+    "//garnet/public/lib/fidl/rust/fidl",
     "//garnet/public/rust/fdio",
     "//garnet/public/rust/fuchsia-async",
     "//garnet/public/rust/fuchsia-zircon",
     "//third_party/rust_crates:failure",
     "//zircon/public/fidl/fuchsia-hardware-telephony-transport:fuchsia-hardware-telephony-transport-rustc",
+    "//zircon/public/fidl/fuchsia-telephony-snoop:fuchsia-telephony-snoop-rustc",
   ]
 }
diff --git a/src/connectivity/telephony/lib/qmi/src/lib.rs b/src/connectivity/telephony/lib/qmi/src/lib.rs
index 1a48ca1..0b59b9c 100644
--- a/src/connectivity/telephony/lib/qmi/src/lib.rs
+++ b/src/connectivity/telephony/lib/qmi/src/lib.rs
@@ -3,11 +3,28 @@
 // found in the LICENSE file.
 #![feature(async_await, await_macro)]
 
+use fidl::endpoints::{create_endpoints, ServerEnd};
 use fidl_fuchsia_hardware_telephony_transport::QmiProxy;
+use fidl_fuchsia_telephony_snoop::PublisherMarker as QmiSnoopMarker;
 use fuchsia_async as fasync;
 use fuchsia_zircon as zx;
 use std::fs::File;
 
+/// Connect to transport driver, and pass a channel handle for snoop Qmi messages
+pub async fn connect_snoop_channel(
+    device: &File,
+) -> Result<ServerEnd<QmiSnoopMarker>, failure::Error> {
+    let qmi_channel: fasync::Channel = fasync::Channel::from_channel(fdio::clone_channel(device)?)?;
+    let interface = QmiProxy::new(qmi_channel);
+    let (client_side, server_side) = create_endpoints::<QmiSnoopMarker>()?;
+    match await!(interface.set_snoop_channel(client_side)) {
+        Ok(_r) => Ok(server_side),
+        Err(e) => Err(e.into()),
+    }
+}
+
+/// Connect to transport driver, and pass a channel handle for Tx/Rx Qmi messages
+/// to/from ril-qmi
 pub async fn connect_transport_device(device: &File) -> Result<zx::Channel, failure::Error> {
     let qmi_channel: fasync::Channel = fasync::Channel::from_channel(fdio::clone_channel(device)?)?;
     let interface = QmiProxy::new(qmi_channel);
diff --git a/src/connectivity/telephony/tools/BUILD.gn b/src/connectivity/telephony/tools/BUILD.gn
index 339907f..d1bfea5 100644
--- a/src/connectivity/telephony/tools/BUILD.gn
+++ b/src/connectivity/telephony/tools/BUILD.gn
@@ -5,6 +5,7 @@
 group("tools") {
   testonly = true
   deps = [
+    "qmi-snoop",
     "ril-ctl",
   ]
 }
diff --git a/src/connectivity/telephony/tools/qmi-snoop/BUILD.gn b/src/connectivity/telephony/tools/qmi-snoop/BUILD.gn
new file mode 100644
index 0000000..c4f3179
--- /dev/null
+++ b/src/connectivity/telephony/tools/qmi-snoop/BUILD.gn
@@ -0,0 +1,36 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("bin") {
+  name = "qmi_snoop"
+  edition = "2018"
+
+  deps = [
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fuchsia-async",
+    "//src/connectivity/telephony/lib/qmi",
+    "//third_party/rust_crates:failure",
+    "//third_party/rust_crates:futures-preview",
+    "//third_party/rust_crates:structopt",
+    "//zircon/public/fidl/fuchsia-telephony-snoop:fuchsia-telephony-snoop-rustc",
+  ]
+}
+
+package("qmi-snoop") {
+  deps = [
+    ":bin",
+  ]
+
+  binaries = [
+    {
+      name = "qmi_snoop"
+      dest = "qmi-snoop"
+      path = "rust_crates/qmi_snoop"
+      shell = true
+    },
+  ]
+}
diff --git a/src/connectivity/telephony/tools/qmi-snoop/src/main.rs b/src/connectivity/telephony/tools/qmi-snoop/src/main.rs
new file mode 100644
index 0000000..1b62a15
--- /dev/null
+++ b/src/connectivity/telephony/tools/qmi-snoop/src/main.rs
@@ -0,0 +1,70 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! qmi-snoop is used for snooping Qmi messages sent/received by transport driver
+
+#![feature(async_await, await_macro)]
+
+use {
+    failure::{Error, ResultExt},
+    fidl::endpoints::ServerEnd,
+    fidl_fuchsia_telephony_snoop::{
+        Message as SnoopMessage, PublisherMarker as QmiSnoopMarker,
+        PublisherRequest as QmiSnoopRequest, PublisherRequestStream as QmiSnoopRequestStream,
+    },
+    fuchsia_async as fasync, futures,
+    futures::stream::TryStreamExt,
+    qmi,
+    std::fs::File,
+    std::path::PathBuf,
+    structopt::StructOpt,
+};
+
+#[derive(StructOpt, Debug)]
+#[structopt(name = "qmi-snoop")]
+struct Opt {
+    /// Device path (e.g. /dev/class/qmi-transport/000)
+    #[structopt(short = "d", long = "device", parse(from_os_str))]
+    device: Option<PathBuf>,
+}
+
+pub fn main() -> Result<(), Error> {
+    let mut exec = fasync::Executor::new().context("error creating event loop")?;
+    let args = Opt::from_args();
+    let device = match args.device {
+        Some(device_path) => device_path,
+        None => PathBuf::from("/dev/class/qmi-transport/000"),
+    };
+    let fut = async move {
+        eprintln!("Connecting with exclusive access to {}..", device.display());
+        let file: File = File::open(device)?;
+        let snoop_endpoint_server_side: ServerEnd<QmiSnoopMarker> =
+            await!(qmi::connect_snoop_channel(&file))?;
+        let mut request_stream: QmiSnoopRequestStream = snoop_endpoint_server_side.into_stream()?;
+        while let Ok(Some(QmiSnoopRequest::SendMessage { msg, control_handle: _ })) =
+            await!(request_stream.try_next())
+        {
+            let qmi_message = match msg {
+                SnoopMessage::QmiMessage(m) => Some(m),
+            };
+            match qmi_message {
+                Some(message) => {
+                    let slice = &message.opaque_bytes;
+                    eprint!(
+                        "Received msg direction: {:?}, timestamp: {}, msg:",
+                        message.direction, message.timestamp
+                    );
+                    for element in slice.iter() {
+                        eprint!(" {}", element);
+                    }
+                    eprint!("\n");
+                }
+                None => {}
+            }
+        }
+        eprintln!("unexpected msg received");
+        Ok::<_, Error>(())
+    };
+    exec.run_singlethreaded(fut)
+}
diff --git a/zircon/system/fidl/BUILD.gn b/zircon/system/fidl/BUILD.gn
index 5202474..5800afe 100644
--- a/zircon/system/fidl/BUILD.gn
+++ b/zircon/system/fidl/BUILD.gn
@@ -74,6 +74,7 @@
     "fuchsia-sysmem",
     "fuchsia-tee",
     "fuchsia-tee-manager",
+    "fuchsia-telephony-snoop",
     "fuchsia-tracelink",
     "fuchsia-tracing-kernel",
     "fuchsia-usb-debug",
diff --git a/zircon/system/fidl/fuchsia-hardware-telephony-transport/BUILD.gn b/zircon/system/fidl/fuchsia-hardware-telephony-transport/BUILD.gn
index 29fdc15..cb1b8d7 100644
--- a/zircon/system/fidl/fuchsia-hardware-telephony-transport/BUILD.gn
+++ b/zircon/system/fidl/fuchsia-hardware-telephony-transport/BUILD.gn
@@ -8,4 +8,8 @@
   sources = [
     "qmi.fidl",
   ]
+
+  public_deps = [
+    "$zx/system/fidl/fuchsia-telephony-snoop:fuchsia-telephony-snoop",
+  ]
 }
diff --git a/zircon/system/fidl/fuchsia-hardware-telephony-transport/qmi.fidl b/zircon/system/fidl/fuchsia-hardware-telephony-transport/qmi.fidl
index dc6556e..56cd55f 100644
--- a/zircon/system/fidl/fuchsia-hardware-telephony-transport/qmi.fidl
+++ b/zircon/system/fidl/fuchsia-hardware-telephony-transport/qmi.fidl
@@ -5,6 +5,7 @@
 library fuchsia.hardware.telephony.transport;
 
 using zx;
+using fuchsia.telephony.snoop as qmi_snoop;
 
 [Layout = "Simple"]
 protocol Qmi {
@@ -14,4 +15,7 @@
     /// Configure the network used by the transport
     /// Currently only sets network up/down
     SetNetwork(bool connected) -> ();
+
+    /// Pass an interface for QMI message snooping
+    SetSnoopChannel(qmi_snoop.Publisher interface) -> () error zx.status;
 };
diff --git a/zircon/system/fidl/fuchsia-telephony-snoop/BUILD.gn b/zircon/system/fidl/fuchsia-telephony-snoop/BUILD.gn
new file mode 100644
index 0000000..efff89e
--- /dev/null
+++ b/zircon/system/fidl/fuchsia-telephony-snoop/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("$zx/public/gn/fidl.gni")
+
+fidl_library("fuchsia-telephony-snoop") {
+  sources = [
+    "tel-snoop.fidl",
+  ]
+}
diff --git a/zircon/system/fidl/fuchsia-telephony-snoop/tel-snoop.fidl b/zircon/system/fidl/fuchsia-telephony-snoop/tel-snoop.fidl
new file mode 100644
index 0000000..7f6ad1e
--- /dev/null
+++ b/zircon/system/fidl/fuchsia-telephony-snoop/tel-snoop.fidl
@@ -0,0 +1,30 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library fuchsia.telephony.snoop;
+
+using zx;
+
+enum Direction {
+    FROM_MODEM = 1;
+    TO_MODEM = 2;
+};
+
+struct QmiMessage {
+    zx.time timestamp;
+    Direction direction;
+    bool is_partial_copy;
+    array<uint8>:256 opaque_bytes;
+};
+
+//TODO(jiamingw): change it to xunion after transport driver is converted to llcpp
+union Message {
+    QmiMessage qmi_message;
+};
+
+/// Protocol for forwarding QMI messages from driver to Snoop CLI
+[Layout = "Simple"]
+protocol Publisher {
+    SendMessage(Message msg);
+};