[tel] AT Command transport

Add support for a simple queue for AT messages to the
modem.

Test:

$ fx set core.x64 --with-base bundles:tools,//src/connectivity/telephony,//src/connectivity/telephony:tests
$ fx qemu -kN
$ runtests -t tel_fake_at_query

Change-Id: I441551e78327ffd6df183acabb3af15573076be9
diff --git a/garnet/packages/prod/BUILD.gn b/garnet/packages/prod/BUILD.gn
index 09734a9..3309368 100644
--- a/garnet/packages/prod/BUILD.gn
+++ b/garnet/packages/prod/BUILD.gn
@@ -303,6 +303,8 @@
   public_deps = [
     "//src/connectivity/telephony/drivers/qmi-fake-transport",
     "//src/connectivity/telephony/drivers/qmi-usb-transport",
+    "//src/connectivity/telephony/ril-at",
+    "//src/connectivity/telephony/ril-at:ril-at-tests",
     "//src/connectivity/telephony/ril-qmi",
     "//src/connectivity/telephony/ril-qmi:ril-qmi-tests",
     "//src/connectivity/telephony/telephony",
diff --git a/sdk/fidl/fuchsia.telephony.ril/ril.fidl b/sdk/fidl/fuchsia.telephony.ril/ril.fidl
index 3d2488e..5aab3ac 100644
--- a/sdk/fidl/fuchsia.telephony.ril/ril.fidl
+++ b/sdk/fidl/fuchsia.telephony.ril/ril.fidl
@@ -71,4 +71,7 @@
 
     /// Signal Strength (dBm) for LTE networks (total received wideband power).
     GetSignalStrength() -> (float32 dbm) error RilError;
+
+    /// Raw string to send to modem for development.
+    RawCommand(string command) -> (string result) error RilError;
 };
diff --git a/src/connectivity/telephony/BUILD.gn b/src/connectivity/telephony/BUILD.gn
index 2456bb9..e801eec 100644
--- a/src/connectivity/telephony/BUILD.gn
+++ b/src/connectivity/telephony/BUILD.gn
@@ -7,6 +7,7 @@
   public_deps = [
     ":tests",
     "//src/connectivity/telephony/config",
+    "//src/connectivity/telephony/ril-at",
     "//src/connectivity/telephony/ril-qmi",
     "//src/connectivity/telephony/telephony",
     "//src/connectivity/telephony/tools",
diff --git a/src/connectivity/telephony/ril-at/BUILD.gn b/src/connectivity/telephony/ril-at/BUILD.gn
new file mode 100644
index 0000000..81898b3
--- /dev/null
+++ b/src/connectivity/telephony/ril-at/BUILD.gn
@@ -0,0 +1,56 @@
+# 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")
+import("//build/test/test_package.gni")
+import("//build/testing/environments.gni")
+
+rustc_binary("bin") {
+  name = "ril_at"
+  edition = "2018"
+  with_unit_tests = true
+
+  deps = [
+    "//sdk/fidl/fuchsia.telephony.ril:fuchsia.telephony.ril-rustc",
+    "//src/connectivity/telephony/lib/qmi-protocol",
+    "//src/lib/fidl/rust/fidl",
+    "//src/lib/fuchsia-async",
+    "//src/lib/fuchsia-component",
+    "//src/lib/syslog/rust:syslog",
+    "//src/lib/zircon/rust:fuchsia-zircon",
+    "//third_party/rust_crates:anyhow",
+    "//third_party/rust_crates:bytes",
+    "//third_party/rust_crates:futures",
+    "//third_party/rust_crates:log",
+    "//third_party/rust_crates:parking_lot",
+  ]
+}
+
+package("ril-at") {
+  deps = [ ":bin" ]
+
+  binary = "ril_at"
+
+  meta = [
+    {
+      path = rebase_path("meta/ril-at.cmx")
+      dest = "ril-at.cmx"
+    },
+  ]
+}
+
+unittest_package("ril-at-tests") {
+  package_name = "ril-at-tests"
+
+  deps = [ ":bin_test" ]
+
+  tests = [
+    {
+      name = "ril_at_bin_test"
+      dest = "ril-at-tests"
+      environments = basic_envs
+    },
+  ]
+}
diff --git a/src/connectivity/telephony/ril-at/meta/ril-at.cmx b/src/connectivity/telephony/ril-at/meta/ril-at.cmx
new file mode 100644
index 0000000..2015a8d
--- /dev/null
+++ b/src/connectivity/telephony/ril-at/meta/ril-at.cmx
@@ -0,0 +1,10 @@
+{
+    "program": {
+        "binary": "bin/app"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.logger.LogSink"
+        ]
+    }
+}
diff --git a/src/connectivity/telephony/ril-at/src/main.rs b/src/connectivity/telephony/ril-at/src/main.rs
new file mode 100644
index 0000000..5e6a5e0
--- /dev/null
+++ b/src/connectivity/telephony/ril-at/src/main.rs
@@ -0,0 +1,162 @@
+// Copyright 2020 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.
+
+use crate::transport::AtTransport;
+use anyhow::Error;
+use fidl_fuchsia_telephony_ril::*;
+use fuchsia_async as fasync;
+use fuchsia_component::server::ServiceFs;
+use fuchsia_syslog::{self as syslog, macros::*};
+use fuchsia_zircon as zx;
+use futures::{
+    future::Future,
+    lock::Mutex,
+    task::{AtomicWaker, Context},
+    StreamExt, TryFutureExt, TryStreamExt,
+};
+use std::{pin::Pin, sync::Arc, task::Poll};
+
+mod transport;
+
+pub struct AtModem {
+    transport: Option<AtTransport>,
+    waker: AtomicWaker,
+}
+
+impl AtModem {
+    pub fn new() -> Self {
+        AtModem { transport: None, waker: AtomicWaker::new() }
+    }
+
+    async fn fidl_service_async(modem: Arc<Mutex<AtModem>>, mut stream: SetupRequestStream) -> () {
+        let mut modem = modem.lock().await;
+        let res = stream.next().await;
+        if let Some(Ok(SetupRequest::ConnectTransport { channel, responder })) = res {
+            let status = modem.connect_transport(channel);
+            fx_log_info!("Connecting the service to the transport driver: {}", status);
+            if status {
+                let _result = responder.send(&mut Ok(()));
+            } else {
+                let _result = responder.send(&mut Err(RilError::TransportError));
+            }
+        }
+    }
+
+    pub fn fidl_service(modem: Arc<Mutex<AtModem>>) -> impl Fn(SetupRequestStream) -> () {
+        move |stream| fasync::spawn(AtModem::fidl_service_async(modem.clone(), stream))
+    }
+
+    fn connect_transport(&mut self, chan: zx::Channel) -> bool {
+        fx_log_info!("Connecting the transport");
+        match fasync::Channel::from_channel(chan) {
+            Ok(chan) => {
+                if chan.is_closed() {
+                    fx_log_err!("The transport channel is not open");
+                    return false;
+                }
+                self.set_transport(AtTransport::new(chan));
+                true
+            }
+            Err(_) => {
+                fx_log_err!("Failed to convert a zircon channel to a fasync one");
+                false
+            }
+        }
+    }
+
+    fn set_transport(&mut self, transport: AtTransport) {
+        self.transport = Some(transport);
+        self.waker.wake();
+    }
+}
+
+//Wait for transport setup.
+impl<'modem> Future for &AtModem {
+    type Output = ();
+
+    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        match self.transport {
+            Some(_) => Poll::Ready(()),
+            None => {
+                self.waker.register(cx.waker());
+                Poll::Pending
+            }
+        }
+    }
+}
+
+/// Craft a AT Query given a handle and a client connection. Handles common error paths.
+/// For more specialized interactions with the modem, prefer to call `client.send_msg()` directly.
+macro_rules! at_query {
+    ($responder:expr, $transport:expr, $query:expr) => {{
+        match $transport.send_msg($query).await {
+            Ok(at) => at,
+            Err(e) => {
+                fx_log_err!("Transport Error: {:?}", e);
+                return $responder.send(&mut Err(RilError::TransportError));
+            }
+        }
+    }};
+}
+
+struct FrilService;
+impl FrilService {
+    pub fn fidl_service(
+        modem: Arc<Mutex<AtModem>>,
+    ) -> impl Fn(RadioInterfaceLayerRequestStream) -> () {
+        move |stream: RadioInterfaceLayerRequestStream| {
+            fx_log_info!("New client connecting to the Fuchsia RIL");
+            fasync::spawn(FrilService::fidl_service_async(modem.clone(), stream))
+        }
+    }
+
+    async fn fidl_service_async(
+        modem: Arc<Mutex<AtModem>>,
+        stream: RadioInterfaceLayerRequestStream,
+    ) {
+        stream
+            .try_for_each(move |req| Self::handle_request(modem.clone(), req))
+            .unwrap_or_else(|e| fx_log_err!("Error running {:?}", e))
+            .await
+    }
+
+    async fn handle_request(
+        modem: Arc<Mutex<AtModem>>,
+        request: RadioInterfaceLayerRequest,
+    ) -> Result<(), fidl::Error> {
+        let mut modem = modem.lock().await;
+        (&(*modem)).await; // Await transport setup.
+        // This await  is ok since the transport has been set up.
+        let transport = (&mut modem.transport).as_mut().unwrap(); 
+        match request {
+            RadioInterfaceLayerRequest::RawCommand { command, responder } => {
+                let resp = at_query!(responder, transport, command);
+
+                responder.send(&mut Ok(resp))?;
+            }
+            // Translate requests here
+            _ => (),
+        }
+        Ok(())
+    }
+}
+
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+    syslog::init_with_tags(&["ril-at"]).expect("Can't init logger");
+    fx_log_info!("Starting ril-at...");
+
+    let modem = Arc::new(Mutex::new(AtModem::new()));
+
+    let mut fs = ServiceFs::new_local();
+    let mut dir = fs.dir("svc");
+
+    // Add connection to upstream clients.
+    dir.add_fidl_service(FrilService::fidl_service(modem.clone()));
+    // Add connection to downstream driver.
+    dir.add_fidl_service(AtModem::fidl_service(modem.clone()));
+
+    fs.take_and_serve_directory_handle()?;
+    Ok(fs.collect::<()>().await)
+}
diff --git a/src/connectivity/telephony/ril-at/src/transport.rs b/src/connectivity/telephony/ril-at/src/transport.rs
new file mode 100644
index 0000000..824b76e
--- /dev/null
+++ b/src/connectivity/telephony/ril-at/src/transport.rs
@@ -0,0 +1,123 @@
+// 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.
+
+use fuchsia_async as fasync;
+use fuchsia_zircon as zx;
+use futures::lock::Mutex;
+use std::{collections::VecDeque, str::Utf8Error, sync::Arc};
+
+#[derive(Clone, Debug)]
+pub enum AtTransportError {
+    ChannelClosed,
+    ClientRead { error: zx::Status },
+    ClientWrite { error: zx::Status },
+    Deserialize { bytes: Vec<u8>, error: Utf8Error },
+}
+
+struct AtMessage {
+    request: String,
+    response: Option<Result<String, AtTransportError>>,
+}
+
+impl AtMessage {
+    fn new(request: String) -> Arc<Mutex<Self>> {
+        Arc::new(Mutex::new(AtMessage { request, response: None }))
+    }
+}
+
+pub struct AtTransport {
+    messages: Mutex<VecDeque<Arc<Mutex<AtMessage>>>>,
+    transport_channel: Mutex<fasync::Channel>,
+}
+
+impl AtTransport {
+    pub fn new(chan: fasync::Channel) -> Self {
+        AtTransport { transport_channel: Mutex::new(chan), messages: Mutex::new(VecDeque::new()) }
+    }
+
+    pub async fn send_msg(&mut self, string_msg: String) -> Result<String, AtTransportError> {
+        let msg = self.enqueue_message(string_msg).await;
+        self.do_sends_and_recvs().await;
+
+        let msg = msg.lock().await;
+        msg.response.clone().unwrap() // Unwraps the option set in do_sends_and_recvs
+    }
+
+    async fn enqueue_message(&self, string_msg: String) -> Arc<Mutex<AtMessage>> {
+        let msg = AtMessage::new(string_msg);
+
+        let messages = &mut self.messages.lock().await;
+        messages.push_back(msg.clone());
+
+        msg
+    }
+
+    async fn do_sends_and_recvs(&mut self) -> () {
+        // Serialize message send/recvs.
+        let transport_channel = self.transport_channel.lock().await;
+
+        'messages: loop {
+            let message = {
+                let mut messages = self.messages.lock().await;
+                match messages.pop_front() {
+                    None => break 'messages,
+                    Some(m) => m,
+                }
+            };
+            let mut message = message.lock().await;
+
+            let response = self.send_one(&transport_channel, &message.request).await;
+            message.response = Some(response);
+        }
+    }
+
+    async fn send_one(
+        &self,
+        transport: &fasync::Channel,
+        request: &String,
+    ) -> Result<String, AtTransportError> {
+        let request_bytes = Self::serialize_request(request);
+
+        transport
+            .write(&request_bytes, /* no handles */ &mut vec![])
+            .map_err(|error| AtTransportError::ClientWrite { error })?;
+
+        // Loop over unsolicited events until we get a response or an error.
+        loop {
+            let response_buf = Self::recv(transport).await?;
+            let response = Self::deserialize_response(response_buf.bytes().to_vec())?;
+
+            if Self::is_unsolicited_event(&response) {
+                // TODO Handle these
+            } else {
+                return Ok(response);
+            }
+        }
+    }
+
+    fn serialize_request(request: &String) -> Vec<u8> {
+        request.as_bytes().to_vec()
+    }
+
+    fn deserialize_response(response_bytes: Vec<u8>) -> Result<String, AtTransportError> {
+        String::from_utf8(response_bytes).map_err(|error| AtTransportError::Deserialize {
+            bytes: error.as_bytes().to_vec(),
+            error: error.utf8_error(),
+        })
+    }
+
+    fn is_unsolicited_event(_response: &String) -> bool {
+        // TODO Fix this.
+        false
+    }
+
+    async fn recv(transport_channel: &fasync::Channel) -> Result<zx::MessageBuf, AtTransportError> {
+        let mut response_buf = zx::MessageBuf::new();
+        match transport_channel.recv_msg(&mut response_buf).await {
+            Ok(()) => return Ok(response_buf),
+            Err(zx::Status::PEER_CLOSED) => return Err(AtTransportError::ChannelClosed),
+            Err(error) => return Err(AtTransportError::ClientRead { error }),
+        }
+    }
+}
diff --git a/src/connectivity/telephony/ril-qmi/src/main.rs b/src/connectivity/telephony/ril-qmi/src/main.rs
index 874f724..2ad62a7 100644
--- a/src/connectivity/telephony/ril-qmi/src/main.rs
+++ b/src/connectivity/telephony/ril-qmi/src/main.rs
@@ -187,6 +187,9 @@
                     responder.send(&mut Ok(RadioPowerState::Off))?
                 }
             }
+            RadioInterfaceLayerRequest::RawCommand { command: _command, responder } => {
+                responder.send(&mut Err(RilError::UnknownError))?
+            }
         }
         Ok(())
     }
diff --git a/src/connectivity/telephony/tests/BUILD.gn b/src/connectivity/telephony/tests/BUILD.gn
index 5bd2a3c..6bdd3f3 100644
--- a/src/connectivity/telephony/tests/BUILD.gn
+++ b/src/connectivity/telephony/tests/BUILD.gn
@@ -15,6 +15,7 @@
   public_deps = [
     ":devmgr-manifest",
     "component-integration:fake-at-driver-test",
+    "component-integration:fake-at-query",
     "component-integration:fake-qmi-query",
     "component-integration:snooper",
     "driver-integration:telephony-qmi-usb-integration-test",
@@ -37,6 +38,10 @@
       environments = basic_envs
     },
     {
+      name = "tel_fake_at_query"
+      environments = basic_envs
+    },
+    {
       name = "tel_fake_at_driver_test"
       environments = basic_envs
     },
diff --git a/src/connectivity/telephony/tests/component-integration/BUILD.gn b/src/connectivity/telephony/tests/component-integration/BUILD.gn
index a5564bd..47e19f4 100644
--- a/src/connectivity/telephony/tests/component-integration/BUILD.gn
+++ b/src/connectivity/telephony/tests/component-integration/BUILD.gn
@@ -12,6 +12,10 @@
   public_deps = [ "fake-qmi-query" ]
 }
 
+group("fake-at-query") {
+  testonly = true
+  public_deps = [ "fake-at-query" ]
+}
 group("snooper") {
   testonly = true
   public_deps = [ "snooper:multi-clients" ]
diff --git a/src/connectivity/telephony/tests/component-integration/fake-at-query/BUILD.gn b/src/connectivity/telephony/tests/component-integration/fake-at-query/BUILD.gn
new file mode 100644
index 0000000..2bd48f7
--- /dev/null
+++ b/src/connectivity/telephony/tests/component-integration/fake-at-query/BUILD.gn
@@ -0,0 +1,31 @@
+# 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("//build/rust/rustc_test.gni")
+
+rustc_test("fake-at-query") {
+  name = "tel_fake_at_query"
+  edition = "2018"
+  deps = [
+    "//garnet/lib/rust/files_async",
+    "//sdk/fidl/fuchsia.telephony.ril:fuchsia.telephony.ril-rustc",
+    "//src/connectivity/telephony/lib/qmi",
+    "//src/connectivity/telephony/lib/tel-devmgr/fidl:devmgr-rustc",
+    "//src/connectivity/telephony/tests/tel-dev:tel_dev",
+    "//src/lib/fdio/rust:fdio",
+    "//src/lib/fidl/rust/fidl",
+    "//src/lib/fuchsia-async",
+    "//src/lib/fuchsia-component",
+    "//src/lib/storage/fuchsia-vfs-watcher",
+    "//src/lib/syslog/rust:syslog",
+    "//src/lib/zircon/rust:fuchsia-zircon",
+    "//third_party/rust_crates:anyhow",
+    "//third_party/rust_crates:futures",
+    "//third_party/rust_crates:pin-utils",
+    "//third_party/rust_crates:thiserror",
+    "//zircon/system/fidl/fuchsia-device:fuchsia-device-rustc",
+    "//zircon/system/fidl/fuchsia-io:fuchsia-io-rustc",
+  ]
+  non_rust_deps = [ "//zircon/public/lib/syslog" ]
+}
diff --git a/src/connectivity/telephony/tests/component-integration/fake-at-query/src/lib.rs b/src/connectivity/telephony/tests/component-integration/fake-at-query/src/lib.rs
new file mode 100644
index 0000000..1e9b8bf
--- /dev/null
+++ b/src/connectivity/telephony/tests/component-integration/fake-at-query/src/lib.rs
@@ -0,0 +1,87 @@
+// 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.
+
+use {
+    anyhow::{Context as _, Error},
+    fidl_fuchsia_telephony_ril::{RadioInterfaceLayerMarker, SetupMarker},
+    fuchsia_component::{
+        client::{launch, launcher},
+        fuchsia_single_component_package_url,
+    },
+    fuchsia_syslog::{self as syslog, macros::*},
+    futures::future::{join_all, Future},
+    futures::lock::Mutex,
+    qmi as tel_ctl, //Todo: rename qmi module
+    std::pin::Pin,
+    std::sync::Arc,
+    tel_dev::component_test::*,
+};
+
+// Send this command to attempt to make a call on the test driver and fail.
+const AT_CMD_REQ_ATD_STR: &str = "ATD\r";
+const AT_CMD_RESP_NO_CARRIER_STR: &str = "NO CARRIER\r";
+
+// Send this string to request the test driver sends back an invalid text string.
+const AT_CMD_REQ_INVALID_UNICODE_STR: &str = "INVALID UNICODE";
+
+const RIL_URL: &str = fuchsia_single_component_package_url!("ril-at");
+
+// TODO(kehrt) Split this into multiple tests.  Unfortunately, test setup and teardown multiple
+// times causes a variety of errors I don't understand yet.
+#[fuchsia_async::run_singlethreaded(test)]
+async fn at_one_query_test() -> Result<(), Error> {
+    syslog::init_with_tags(&["at-query-test"]).expect("Can't init logger");
+
+    //Setup
+    const TEL_PATH: &str = "class/at-transport";
+    let found_device =
+        get_fake_device_in_isolated_devmgr(TEL_PATH).await.expect("getting fake device");
+    let launcher = launcher().context("Failed to open launcher service")?;
+    let chan = tel_ctl::connect_transport_device(&found_device).await?;
+    let app =
+        launch(&launcher, RIL_URL.to_string(), None).context("Failed to launch ril-at service")?;
+    let ril_modem_setup = app.connect_to_service::<SetupMarker>()?;
+    ril_modem_setup.connect_transport(chan).await?.expect("make sure telephony svc is running");
+    let ril_modem = app.connect_to_service::<RadioInterfaceLayerMarker>()?;
+
+    // Test sending and receiving one message.
+    fx_log_err!("sending ATD");
+    let resp = ril_modem.raw_command(&AT_CMD_REQ_ATD_STR).await?.expect("error sending get info");
+    assert_eq!(resp, AT_CMD_RESP_NO_CARRIER_STR);
+    fx_log_err!("received and verified responses");
+
+    // Test sending a message that causes a parse error for the response.
+    fx_log_err!("sending a request which expects an error response");
+    let resp = ril_modem
+        .raw_command(&AT_CMD_REQ_INVALID_UNICODE_STR)
+        .await
+        .expect("error sending get info");
+    assert!(resp.is_err());
+    fx_log_err!("received and verified responses");
+
+    // Test interleaving a bunch of sends and receives.  let ril_modem_arc = Arc::new(Mutex::new(ril_modem));
+    let ril_modem_arc = Arc::new(Mutex::new(ril_modem));
+    let mut vec: Vec<Pin<Box<dyn Future<Output = (i32, String)>>>> = Vec::new();
+    for x in 0..100 {
+        let ril_modem_arc = ril_modem_arc.clone();
+        vec.push(Box::pin(async move {
+            let send_string = format!("RACE{:}", x);
+            let ril_modem_arc = ril_modem_arc.lock().await;
+            let recv_result = ril_modem_arc.raw_command(&send_string).await;
+            (x, recv_result.unwrap().unwrap())
+        }))
+    }
+
+    let joined = join_all(vec).await;
+
+    for (x, recv_result) in joined.into_iter() {
+        let recv_string = format!("ECAR{:}", x);
+        assert!(recv_string == recv_result);
+    }
+    // Tear down
+    unbind_fake_device(&found_device)?;
+    fx_log_err!("unbinded device");
+    validate_removal_of_fake_device(TEL_PATH).await.expect("validate removal of device");
+    Ok(())
+}
diff --git a/src/connectivity/telephony/tests/component-integration/fake-qmi-query/src/lib.rs b/src/connectivity/telephony/tests/component-integration/fake-qmi-query/src/lib.rs
index 3dfc73f..04bb78b 100644
--- a/src/connectivity/telephony/tests/component-integration/fake-qmi-query/src/lib.rs
+++ b/src/connectivity/telephony/tests/component-integration/fake-qmi-query/src/lib.rs
@@ -4,7 +4,6 @@
 
 use {
     anyhow::{Context as _, Error},
-    fidl_fuchsia_io,
     fidl_fuchsia_telephony_ril::{RadioInterfaceLayerMarker, SetupMarker},
     fuchsia_component::{
         client::{launch, launcher},
@@ -12,8 +11,7 @@
     },
     fuchsia_syslog::{self as syslog, macros::*},
     qmi,
-    std::path::Path,
-    tel_dev::{component_test::*, isolated_devmgr},
+    tel_dev::component_test::*,
 };
 
 const RIL_URL: &str = fuchsia_single_component_package_url!("ril-qmi");
@@ -23,17 +21,8 @@
 async fn qmi_query_test() -> Result<(), Error> {
     syslog::init_with_tags(&["qmi-query-test"]).expect("Can't init logger");
     const TEL_PATH: &str = "class/qmi-transport";
-    let tel_dir =
-        isolated_devmgr::open_dir_in_isolated_devmgr(TEL_PATH).expect("opening qmi-transport dir");
-    let directory_proxy = fidl_fuchsia_io::DirectoryProxy::new(
-        fuchsia_async::Channel::from_channel(fdio::clone_channel(&tel_dir)?)?,
-    );
-    let tel_devices = files_async::readdir(&directory_proxy).await?;
-    let last_device: &files_async::DirEntry = tel_devices.last().expect("no device found");
-    let found_device_path = Box::new(Path::new(TEL_PATH).join(last_device.name.clone()));
-    assert_eq!(tel_devices.len(), 1);
-    fx_log_info!("connecting to {:?}", &*found_device_path);
-    let found_device = isolated_devmgr::open_file_in_isolated_devmgr(*found_device_path).unwrap();
+    let found_device =
+        get_fake_device_in_isolated_devmgr(TEL_PATH).await.expect("getting fake device");
     let launcher = launcher().context("Failed to open launcher service")?;
     let chan = qmi::connect_transport_device(&found_device).await?;
     let app =
@@ -41,6 +30,7 @@
     let ril_modem_setup = app.connect_to_service::<SetupMarker>()?;
     ril_modem_setup.connect_transport(chan).await?.expect("make sure telephony svc is running");
     let ril_modem = app.connect_to_service::<RadioInterfaceLayerMarker>()?;
+
     fx_log_info!("sending a IMEI request");
     let imei = ril_modem.get_device_identity().await?.expect("error sending IMEI request");
     assert_eq!(imei, "359260080168351");
diff --git a/src/connectivity/telephony/tests/fake-drivers/at-fake-transport/fake-device.cc b/src/connectivity/telephony/tests/fake-drivers/at-fake-transport/fake-device.cc
index 6f67d39..d3f8e1ee 100644
--- a/src/connectivity/telephony/tests/fake-drivers/at-fake-transport/fake-device.cc
+++ b/src/connectivity/telephony/tests/fake-drivers/at-fake-transport/fake-device.cc
@@ -13,6 +13,7 @@
 #include <cstdio>
 #include <future>
 #include <string>
+
 #include <ddktl/fidl.h>
 
 namespace fidl_tel_snoop = ::llcpp::fuchsia::telephony::snoop;
@@ -24,6 +25,14 @@
 static const std::string kAtCmdRespManuId = "Sierra Wireless Incorporated\r\rOK\r";
 static const std::string kAtCmdRespErr = "ERROR\r";
 
+static const size_t kRaceDetectionPrefixSize = 4;
+static const std::string kRaceDetectionRequestPrefix = "RACE";
+static const std::string kRaceDetectionResponsePrefix = "ECAR";
+
+static const std::string kInvalidUnicodeRequest = "INVALID UNICODE";
+static const size_t kInvalidUnicodeResponseSize = 2;
+static const char* kInvalidUnicodeResponse = "\x80\x81";
+
 constexpr uint32_t kTelCtrlPlanePktMax = 2048;
 
 AtDevice::AtDevice(zx_device_t* device) : Device(device) {}
@@ -52,7 +61,7 @@
 }
 
 void AtDevice::SnoopCtrlMsg(uint8_t* snoop_data, uint32_t snoop_data_len,
-                             fidl_tel_snoop::Direction direction) {
+                            fidl_tel_snoop::Direction direction) {
   if (GetCtrlSnoopChannel()) {
     fidl_tel_snoop::Message snoop_msg;
     fidl_tel_snoop::QmiMessage msg;
@@ -64,26 +73,44 @@
     memcpy(msg.opaque_bytes.data_, snoop_data, current_length);
     snoop_msg.set_qmi_message(&msg);
     zxlogf(INFO, "at-fake-transport: snoop msg %u %u %u %u sent\n", msg.opaque_bytes.data_[0],
-           msg.opaque_bytes.data_[1], msg.opaque_bytes.data_[2],
-           msg.opaque_bytes.data_[3]);
+           msg.opaque_bytes.data_[1], msg.opaque_bytes.data_[2], msg.opaque_bytes.data_[3]);
     fidl_tel_snoop::Publisher::Call::SendMessage(zx::unowned_channel(GetCtrlSnoopChannel().get()),
                                                  std::move(snoop_msg));
   }
 }
 
 void AtDevice::ReplyCtrlMsg(uint8_t* req, uint32_t req_size, uint8_t* resp, uint32_t resp_size) {
-  zxlogf(INFO, "at-fake-driver: req %u %u %u %u with len %u\n", req[0], req[1], req[2], req[3], req_size);
+  zxlogf(INFO, "at-fake-driver: req %u %u %u %u with len %u\n", req[0], req[1], req[2], req[3],
+         req_size);
   if (0 == memcmp(req, kAtCmdReqAtdStr.c_str(), kAtCmdReqAtdStr.size())) {
     resp_size = std::min(kAtCmdRespNoCarrier.size(), static_cast<std::size_t>(resp_size));
     memcpy(resp, kAtCmdRespNoCarrier.c_str(), resp_size);
-    zxlogf(INFO, "at-fake-driver: resp %u %u %u %u with len %u\n", resp[0], resp[1], resp[2], resp[3], resp_size);
+    zxlogf(INFO, "at-fake-driver: resp %u %u %u %u with len %u\n", resp[0], resp[1], resp[2],
+           resp[3], resp_size);
     sent_fake_at_msg(GetCtrlChannel(), resp, resp_size);
     SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
+
   } else if (0 == memcmp(req, kAtCmdReqAtCgmi.c_str(), kAtCmdReqAtCgmi.size())) {
     resp_size = std::min(kAtCmdRespManuId.size(), static_cast<std::size_t>(resp_size));
     memcpy(resp, kAtCmdRespManuId.c_str(), resp_size);
     sent_fake_at_msg(GetCtrlChannel(), resp, resp_size);
     SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
+
+  } else if (0 == memcmp(req, kInvalidUnicodeRequest.c_str(), kInvalidUnicodeRequest.size())) {
+    resp_size = std::min(kInvalidUnicodeResponseSize, static_cast<std::size_t>(resp_size));
+    memcpy(resp, kInvalidUnicodeResponse, resp_size);
+    sent_fake_at_msg(GetCtrlChannel(), resp, resp_size);
+    SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
+
+  } else if (req_size >= kRaceDetectionPrefixSize &&
+             0 == memcmp(req, kRaceDetectionRequestPrefix.c_str(), kRaceDetectionPrefixSize)) {
+    resp_size = std::min(resp_size, req_size);
+    memcpy(resp, kRaceDetectionResponsePrefix.c_str(), kRaceDetectionPrefixSize);
+    memcpy(resp + kRaceDetectionPrefixSize, req + kRaceDetectionPrefixSize,
+           req_size - kRaceDetectionPrefixSize);
+    sent_fake_at_msg(GetCtrlChannel(), resp, resp_size);
+    SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
+
   } else {
     zxlogf(ERROR, "at-fake-driver: unexpected at msg received\n");
     resp_size = std::min(kAtCmdRespErr.size(), static_cast<std::size_t>(resp_size));
@@ -149,8 +176,7 @@
   // create a port to watch at messages
   zx_status_t status = zx::port::create(0, &GetCtrlChannelPort());
   if (status != ZX_OK) {
-    zxlogf(ERROR, "at-fake-transport: failed to create a port: %s\n",
-           zx_status_get_string(status));
+    zxlogf(ERROR, "at-fake-transport: failed to create a port: %s\n", zx_status_get_string(status));
     return status;
   }
 
diff --git a/src/connectivity/telephony/tests/meta/tel_fake_at_query.cmx b/src/connectivity/telephony/tests/meta/tel_fake_at_query.cmx
new file mode 100644
index 0000000..a1e54ea
--- /dev/null
+++ b/src/connectivity/telephony/tests/meta/tel_fake_at_query.cmx
@@ -0,0 +1,32 @@
+{
+    "facets": {
+        "fuchsia.test": {
+            "injected-services": {
+                "fuchsia.tel.devmgr.IsolatedDevmgr": [
+                    "fuchsia-pkg://fuchsia.com/tel_devmgr_at_component_test#meta/tel_devmgr_at_component_test.cmx"
+                ]
+            }
+        }
+    },
+    "program": {
+        "binary": "test/tel_fake_at_query"
+    },
+    "sandbox": {
+        "boot": [
+            "bin",
+            "driver",
+            "lib"
+        ],
+        "dev": [
+            "class/at-transport",
+            "test/test"
+        ],
+        "services": [
+            "fuchsia.sys.Launcher",
+            "fuchsia.tel.devmgr.IsolatedDevmgr"
+        ],
+        "system": [
+            "driver/at-fake-transport.so"
+        ]
+    }
+}