[wlan][fidl] Add GenericSme in wlandevicemonitor and sme.

This is a new mechanism for establishing a connection to an SME. This
API will eventually fully replace the existing APIs, removing our
dependency on wlanstack.

Bug: 66772
Test: Added unit tests
Change-Id: I5648837057ec3c6a95982b78b170214be576fdfe
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/683843
API-Review: Rebecca Silberstein <silberst@google.com>
Commit-Queue: Dylan Swiggett <swiggett@google.com>
Reviewed-by: Kiet Tran <kiettran@google.com>
Reviewed-by: Rebecca Silberstein <silberst@google.com>
diff --git a/sdk/fidl/fuchsia.wlan.device.service/service.fidl b/sdk/fidl/fuchsia.wlan.device.service/service.fidl
index c8d6601..210c82a 100644
--- a/sdk/fidl/fuchsia.wlan.device.service/service.fidl
+++ b/sdk/fidl/fuchsia.wlan.device.service/service.fidl
@@ -75,6 +75,7 @@
     phy_id uint16;
     assigned_iface_id uint16;
     iface client_end:fuchsia.wlan.mlme.MLME;
+    generic_sme server_end:fuchsia.wlan.sme.GenericSme;
 };
 
 type AddIfaceResponse = struct {
@@ -294,4 +295,21 @@
     }) -> (struct {
         status int32;
     });
+
+    // SME methods
+    GetClientSme(struct {
+        iface_id uint16;
+    }) -> (resource struct {
+        sme client_end:fuchsia.wlan.sme.ClientSme;
+    }) error zx.status;
+    GetApSme(struct {
+        iface_id uint16;
+    }) -> (resource struct {
+        sme client_end:fuchsia.wlan.sme.ApSme;
+    }) error zx.status;
+    GetSmeTelemetry(struct {
+        iface_id uint16;
+    }) -> (resource struct {
+        sme client_end:fuchsia.wlan.sme.Telemetry;
+    }) error zx.status;
 };
diff --git a/sdk/fidl/fuchsia.wlan.sme/BUILD.gn b/sdk/fidl/fuchsia.wlan.sme/BUILD.gn
index c4514fb..7a86000 100644
--- a/sdk/fidl/fuchsia.wlan.sme/BUILD.gn
+++ b/sdk/fidl/fuchsia.wlan.sme/BUILD.gn
@@ -19,6 +19,7 @@
     "//sdk/fidl/fuchsia.wlan.ieee80211",
     "//sdk/fidl/fuchsia.wlan.internal",
     "//sdk/fidl/fuchsia.wlan.mesh",
+    "//sdk/fidl/fuchsia.wlan.stats",
     "//zircon/vdso/zx",
   ]
 }
diff --git a/sdk/fidl/fuchsia.wlan.sme/sme.fidl b/sdk/fidl/fuchsia.wlan.sme/sme.fidl
index fded336..3731da7 100644
--- a/sdk/fidl/fuchsia.wlan.sme/sme.fidl
+++ b/sdk/fidl/fuchsia.wlan.sme/sme.fidl
@@ -8,6 +8,7 @@
 using fuchsia.wlan.ieee80211 as ieee80211;
 using fuchsia.wlan.internal;
 using fuchsia.wlan.mesh;
+using fuchsia.wlan.stats;
 using zx;
 
 /// Security protection which should mirror the Protection enum defined in wlan lib common
@@ -296,3 +297,24 @@
         path fuchsia.wlan.mesh.MeshPathTable;
     });
 };
+
+protocol Telemetry {
+    GetCounterStats() -> (struct {
+        stats fuchsia.wlan.stats.IfaceCounterStats;
+    }) error zx.status;
+    GetHistogramStats() -> (struct {
+        stats fuchsia.wlan.stats.IfaceHistogramStats;
+    }) error zx.status;
+};
+
+protocol GenericSme {
+    GetClientSme() -> (resource struct {
+        sme client_end:ClientSme;
+    }) error zx.status;
+    GetApSme() -> (resource struct {
+        sme client_end:ApSme;
+    }) error zx.status;
+    GetSmeTelemetry() -> (resource struct {
+        sme client_end:Telemetry;
+    }) error zx.status;
+};
diff --git a/src/connectivity/wlan/lib/sme/src/serve/mod.rs b/src/connectivity/wlan/lib/sme/src/serve/mod.rs
index 2b86252..90cec97 100644
--- a/src/connectivity/wlan/lib/sme/src/serve/mod.rs
+++ b/src/connectivity/wlan/lib/sme/src/serve/mod.rs
@@ -9,10 +9,14 @@
 use {
     crate::{MlmeRequest, MlmeStream, Station},
     anyhow::format_err,
+    fidl::endpoints::create_endpoints,
     fidl_fuchsia_wlan_common as fidl_common,
     fidl_fuchsia_wlan_mlme::{self as fidl_mlme, MlmeEventStream, MlmeProxy},
+    fidl_fuchsia_wlan_sme as fidl_sme,
     fuchsia_inspect_contrib::auto_persist,
+    fuchsia_zircon as zx,
     futures::{channel::mpsc, future::FutureObj, prelude::*, select},
+    log::{error, warn},
     std::marker::Unpin,
     std::sync::{Arc, Mutex},
     wlan_common::{
@@ -25,12 +29,76 @@
 pub type ApSmeServer = mpsc::UnboundedSender<ap::Endpoint>;
 pub type MeshSmeServer = mpsc::UnboundedSender<mesh::Endpoint>;
 
+#[derive(Clone)]
 pub enum SmeServer {
     Client(ClientSmeServer),
     Ap(ApSmeServer),
     Mesh(MeshSmeServer),
 }
 
+async fn serve_generic_sme(
+    generic_sme: fidl::endpoints::ServerEnd<fidl_sme::GenericSmeMarker>,
+    mut sme_server: SmeServer,
+) -> Result<(), anyhow::Error> {
+    let mut generic_sme_stream = match generic_sme.into_stream() {
+        Ok(stream) => stream,
+        Err(e) => return Err(format_err!("Failed to handle Generic SME stream: {}", e)),
+    };
+    loop {
+        match generic_sme_stream.next().await {
+            // Right now we only support one API per-sme, but in the future we plan to support
+            // multiple and this fn will be more useful.
+            Some(Ok(req)) => {
+                let result = match req {
+                    fidl_sme::GenericSmeRequest::GetClientSme { responder } => {
+                        let (client_end, server_end) =
+                            create_endpoints::<fidl_sme::ClientSmeMarker>()
+                                .expect("failed to create ClientSme");
+                        let mut response = if let SmeServer::Client(server) = &mut sme_server {
+                            server
+                                .send(server_end)
+                                .await
+                                .map(|_| client_end)
+                                .map_err(|_| zx::Status::PEER_CLOSED.into_raw())
+                        } else {
+                            Err(zx::Status::NOT_SUPPORTED.into_raw())
+                        };
+                        responder.send(&mut response)
+                    }
+                    fidl_sme::GenericSmeRequest::GetApSme { responder } => {
+                        let (client_end, server_end) = create_endpoints::<fidl_sme::ApSmeMarker>()
+                            .expect("failed to create ApSme");
+                        let mut response = if let SmeServer::Ap(server) = &mut sme_server {
+                            server
+                                .send(server_end)
+                                .await
+                                .map(|_| client_end)
+                                .map_err(|_| zx::Status::PEER_CLOSED.into_raw())
+                        } else {
+                            Err(zx::Status::NOT_SUPPORTED.into_raw())
+                        };
+                        responder.send(&mut response)
+                    }
+                    fidl_sme::GenericSmeRequest::GetSmeTelemetry { responder } => {
+                        // TODO(fxbug.dev/66772): Support SME Telemetry API
+                        warn!("Requested unsupported SME telemetry API");
+                        responder.send(&mut Err(zx::Status::NOT_SUPPORTED.into_raw()))
+                    }
+                };
+                if let Err(e) = result {
+                    error!("Failed to respond to SME handle request: {}", e);
+                }
+            }
+            Some(Err(e)) => {
+                return Err(format_err!("Generic SME stream failed: {}", e));
+            }
+            None => {
+                return Err(format_err!("Generic SME stream terminated"));
+            }
+        }
+    }
+}
+
 pub fn create_sme(
     cfg: crate::Config,
     mlme_proxy: fidl_mlme::MlmeProxy,
@@ -42,6 +110,7 @@
     hasher: WlanHasher,
     persistence_req_sender: auto_persist::PersistenceReqSender,
     mut shutdown_receiver: mpsc::Receiver<()>,
+    generic_sme: fidl::endpoints::ServerEnd<fidl_sme::GenericSmeMarker>,
 ) -> (SmeServer, impl Future<Output = Result<(), anyhow::Error>>) {
     let device_info = device_info.clone();
     let event_stream = mlme_proxy.take_event_stream();
@@ -75,9 +144,11 @@
             (SmeServer::Mesh(sender), FutureObj::new(Box::new(fut)))
         }
     };
+    let generic_sme_fut = serve_generic_sme(generic_sme, server.clone());
     let sme_fut_with_shutdown = async move {
         select! {
             sme_fut = sme_fut.fuse() => sme_fut,
+            generic_sme_fut = generic_sme_fut.fuse() => generic_sme_fut,
             _ = shutdown_receiver.select_next_some() => Ok(()),
         }
     };
@@ -162,7 +233,7 @@
         fuchsia_inspect::Inspector,
         futures::task::Poll,
         pin_utils::pin_mut,
-        std::sync::Arc,
+        std::{pin::Pin, sync::Arc},
         wlan_common::{
             assert_variant,
             test_utils::fake_features::{
@@ -185,6 +256,8 @@
         let (persistence_req_sender, _persistence_stream) =
             test_utils::create_inspect_persistence_channel();
         let (mut shutdown_sender, shutdown_receiver) = mpsc::channel(1);
+        let (_generic_sme_proxy, generic_sme_server) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
         let (_sme_server, serve_fut) = create_sme(
             crate::Config::default(),
             mlme_proxy,
@@ -196,6 +269,7 @@
             WlanHasher::new(PLACEHOLDER_HASH_KEY),
             persistence_req_sender,
             shutdown_receiver,
+            generic_sme_server,
         );
         pin_mut!(serve_fut);
         assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
@@ -208,7 +282,7 @@
     }
 
     #[test]
-    fn sme_close_endpoint() {
+    fn sme_close_endpoints() {
         let mut exec = fasync::TestExecutor::new().expect("failed to create an executor");
         let (mlme_proxy, _mlme_server) =
             create_proxy::<MlmeMarker>().expect("failed to create MlmeProxy");
@@ -217,6 +291,8 @@
         let (persistence_req_sender, _persistence_stream) =
             test_utils::create_inspect_persistence_channel();
         let (_shutdown_sender, shutdown_receiver) = mpsc::channel(1);
+        let (generic_sme_proxy, generic_sme_server) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
         let (mut sme_server, serve_fut) = create_sme(
             crate::Config::default(),
             mlme_proxy,
@@ -228,6 +304,7 @@
             WlanHasher::new(PLACEHOLDER_HASH_KEY),
             persistence_req_sender,
             shutdown_receiver,
+            generic_sme_server,
         );
         pin_mut!(serve_fut);
         assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
@@ -242,7 +319,123 @@
         pin_mut!(close_fut);
         assert_variant!(exec.run_until_stalled(&mut close_fut), Poll::Ready(_));
 
+        // Also close secondary SME endpoint in the Generic SME.
+        drop(generic_sme_proxy);
+
         // Verify SME future is finished
         assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Ready(Err(_)));
     }
+
+    struct GenericSmeTestHelper {
+        proxy: fidl_sme::GenericSmeProxy,
+        // These values must stay in scope or the SME will terminate, but they
+        // are not relevant to Generic SME tests.
+        _inspector: Inspector,
+        _shutdown_sender: mpsc::Sender<()>,
+        _persistence_stream: mpsc::Receiver<String>,
+        _mlme_server: fidl::endpoints::ServerEnd<fidl_mlme::MlmeMarker>,
+        // Executor goes last to avoid test shutdown failures.
+        exec: fasync::TestExecutor,
+    }
+
+    fn start_generic_sme_test(
+        role: fidl_common::WlanMacRole,
+    ) -> (GenericSmeTestHelper, Pin<Box<impl Future<Output = Result<(), anyhow::Error>>>>) {
+        let mut exec = fasync::TestExecutor::new().expect("failed to create an executor");
+        let inspector = Inspector::new();
+        let (mlme_proxy, mlme_server) =
+            create_proxy::<MlmeMarker>().expect("failed to create MlmeProxy");
+        let iface_tree_holder = IfaceTreeHolder::new(inspector.root().create_child("sme"));
+        let (persistence_req_sender, persistence_stream) =
+            test_utils::create_inspect_persistence_channel();
+        let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
+        let (generic_sme_proxy, generic_sme_server) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
+        let device_info = fidl_mlme::DeviceInfo { role, ..test_utils::fake_device_info([0; 6]) };
+        let (_sme_server, serve_fut) = create_sme(
+            crate::Config::default(),
+            mlme_proxy,
+            &device_info,
+            fake_mac_sublayer_support(),
+            fake_security_support(),
+            fake_spectrum_management_support_empty(),
+            Arc::new(iface_tree_holder),
+            WlanHasher::new(PLACEHOLDER_HASH_KEY),
+            persistence_req_sender,
+            shutdown_receiver,
+            generic_sme_server,
+        );
+        let mut serve_fut = Box::pin(serve_fut);
+        assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+
+        (
+            GenericSmeTestHelper {
+                proxy: generic_sme_proxy,
+                _inspector: inspector,
+                _shutdown_sender: shutdown_sender,
+                _persistence_stream: persistence_stream,
+                _mlme_server: mlme_server,
+                exec,
+            },
+            serve_fut,
+        )
+    }
+
+    #[test]
+    fn generic_sme_get_client() {
+        let (mut helper, mut serve_fut) = start_generic_sme_test(fidl_common::WlanMacRole::Client);
+
+        let mut client_sme_fut = helper.proxy.get_client_sme();
+        assert_variant!(helper.exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+        let client_sme = assert_variant!(helper.exec.run_until_stalled(&mut client_sme_fut), Poll::Ready(Ok(Ok(sme))) => sme);
+        let client_proxy = client_sme.into_proxy().unwrap();
+
+        let mut status_fut = client_proxy.status();
+        assert_variant!(helper.exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+        assert_variant!(
+            helper.exec.run_until_stalled(&mut status_fut),
+            Poll::Ready(Ok(fidl_sme::ClientStatusResponse::Idle(_)))
+        );
+    }
+
+    #[test]
+    fn generic_sme_get_ap_from_client_fails() {
+        let (mut helper, mut serve_fut) = start_generic_sme_test(fidl_common::WlanMacRole::Client);
+
+        let mut client_sme_fut = helper.proxy.get_ap_sme();
+        assert_variant!(helper.exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+        assert_variant!(
+            helper.exec.run_until_stalled(&mut client_sme_fut),
+            Poll::Ready(Ok(Err(_)))
+        );
+    }
+
+    #[test]
+    fn generic_sme_get_ap() {
+        let (mut helper, mut serve_fut) = start_generic_sme_test(fidl_common::WlanMacRole::Ap);
+
+        let mut client_sme_fut = helper.proxy.get_ap_sme();
+        assert_variant!(helper.exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+        let ap_sme = assert_variant!(helper.exec.run_until_stalled(&mut client_sme_fut), Poll::Ready(Ok(Ok(sme))) => sme);
+        let ap_proxy = ap_sme.into_proxy().unwrap();
+
+        let mut status_fut = ap_proxy.status();
+        assert_variant!(helper.exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+        assert_variant!(
+            helper.exec.run_until_stalled(&mut status_fut),
+            Poll::Ready(Ok(fidl_sme::ApStatusResponse { .. }))
+        );
+    }
+
+    #[test]
+    fn generic_sme_get_client_from_ap_fails() {
+        let (mut helper, mut serve_fut) = start_generic_sme_test(fidl_common::WlanMacRole::Ap);
+
+        let mut client_sme_fut = helper.proxy.get_client_sme();
+        assert_variant!(helper.exec.run_until_stalled(&mut serve_fut), Poll::Pending);
+        assert_variant!(
+            helper.exec.run_until_stalled(&mut client_sme_fut),
+            Poll::Ready(Ok(Err(_)))
+        );
+    }
 }
diff --git a/src/connectivity/wlan/wlandevicemonitor/BUILD.gn b/src/connectivity/wlan/wlandevicemonitor/BUILD.gn
index 83dab1b..352db7b 100644
--- a/src/connectivity/wlan/wlandevicemonitor/BUILD.gn
+++ b/src/connectivity/wlan/wlandevicemonitor/BUILD.gn
@@ -30,6 +30,7 @@
   "//sdk/fidl/fuchsia.wlan.device.service:fuchsia.wlan.device.service-rustc",
   "//sdk/fidl/fuchsia.wlan.ieee80211:fuchsia.wlan.ieee80211-rustc",
   "//sdk/fidl/fuchsia.wlan.mlme:fuchsia.wlan.mlme-rustc",
+  "//sdk/fidl/fuchsia.wlan.sme:fuchsia.wlan.sme-rustc",
   "//sdk/fidl/fuchsia.wlan.tap:fuchsia.wlan.tap-rustc",
   "//sdk/lib/device-watcher/rust",
   "//src/connectivity/wlan/lib/common/rust/:wlan-common",
diff --git a/src/connectivity/wlan/wlandevicemonitor/src/device.rs b/src/connectivity/wlan/wlandevicemonitor/src/device.rs
index c04f7c2..25b0637 100644
--- a/src/connectivity/wlan/wlandevicemonitor/src/device.rs
+++ b/src/connectivity/wlan/wlandevicemonitor/src/device.rs
@@ -4,7 +4,7 @@
 
 use {
     anyhow::{format_err, Error},
-    fidl_fuchsia_wlan_device as fidl_wlan_dev,
+    fidl_fuchsia_wlan_device as fidl_wlan_dev, fidl_fuchsia_wlan_sme as fidl_wlan_sme,
     fuchsia_inspect_contrib::inspect_log,
     futures::{
         future::FutureExt,
@@ -34,6 +34,8 @@
     pub id: u16,
     // Information about this iface's PHY.
     pub phy_ownership: PhyOwnership,
+    // The handle for connecting channels to this iface's SME.
+    pub generic_sme: fidl_wlan_sme::GenericSmeProxy,
 }
 
 pub struct PhyDevice {
@@ -43,6 +45,7 @@
 
 pub struct IfaceDevice {
     pub phy_ownership: PhyOwnership,
+    pub generic_sme: fidl_wlan_sme::GenericSmeProxy,
 }
 
 pub type PhyMap = WatchableMap<u16, PhyDevice>;
diff --git a/src/connectivity/wlan/wlandevicemonitor/src/service.rs b/src/connectivity/wlan/wlandevicemonitor/src/service.rs
index 0ee6c58..1e2b252 100644
--- a/src/connectivity/wlan/wlandevicemonitor/src/service.rs
+++ b/src/connectivity/wlan/wlandevicemonitor/src/service.rs
@@ -11,7 +11,7 @@
     fidl::endpoints::create_endpoints,
     fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_device as fidl_dev,
     fidl_fuchsia_wlan_device_service::{self as fidl_svc, DeviceMonitorRequest},
-    fidl_fuchsia_wlan_mlme as fidl_mlme, fuchsia_zircon as zx,
+    fidl_fuchsia_wlan_mlme as fidl_mlme, fidl_fuchsia_wlan_sme as fidl_sme, fuchsia_zircon as zx,
     futures::TryStreamExt,
     log::{error, info},
     std::sync::Arc,
@@ -61,7 +61,10 @@
                         info!("iface #{} started ({:?})", new_iface.id, new_iface.phy_ownership);
                         ifaces.insert(
                             new_iface.id,
-                            IfaceDevice { phy_ownership: new_iface.phy_ownership },
+                            IfaceDevice {
+                                phy_ownership: new_iface.phy_ownership,
+                                generic_sme: new_iface.generic_sme,
+                            },
                         );
 
                         let resp = fidl_svc::CreateIfaceResponse { iface_id: new_iface.id };
@@ -75,6 +78,18 @@
                 let status = into_status_and_opt(result).0;
                 responder.send(status.into_raw())
             }
+            DeviceMonitorRequest::GetClientSme { iface_id, responder } => {
+                let result = get_client_sme(&ifaces, iface_id).await;
+                responder.send(&mut result.map_err(|e| e.into_raw()))
+            }
+            DeviceMonitorRequest::GetApSme { iface_id, responder } => {
+                let result = get_ap_sme(&ifaces, iface_id).await;
+                responder.send(&mut result.map_err(|e| e.into_raw()))
+            }
+            DeviceMonitorRequest::GetSmeTelemetry { iface_id, responder } => {
+                let result = get_sme_telemetry(&ifaces, iface_id).await;
+                responder.send(&mut result.map_err(|e| e.into_raw()))
+            }
         }?;
     }
 
@@ -217,11 +232,22 @@
     })?;
     zx::Status::ok(r.status)?;
 
+    let (generic_sme_client, generic_sme_server) = create_endpoints::<fidl_sme::GenericSmeMarker>()
+        .map_err(|e| {
+            error!("failed to create GenericSmeProxy: {}", e);
+            zx::Status::INTERNAL
+        })?;
+    let generic_sme_proxy = generic_sme_client.into_proxy().map_err(|e| {
+        error!("Error creating GenericSmeProxy: {}", e);
+        zx::Status::INTERNAL
+    })?;
+
     let (status, iface_id) = dev_svc
         .add_iface(&mut fidl_svc::AddIfaceRequest {
             phy_id,
             assigned_iface_id: r.iface_id,
             iface: mlme_client,
+            generic_sme: generic_sme_server,
         })
         .await
         .map_err(|e| {
@@ -234,6 +260,7 @@
     Ok(NewIface {
         id: added_iface.iface_id,
         phy_ownership: device::PhyOwnership { phy_id, phy_assigned_id: r.iface_id },
+        generic_sme: generic_sme_proxy,
     })
 }
 
@@ -266,6 +293,45 @@
     zx::Status::ok(r.status)
 }
 
+async fn get_client_sme(
+    ifaces: &IfaceMap,
+    id: u16,
+) -> Result<fidl::endpoints::ClientEnd<fidl_sme::ClientSmeMarker>, zx::Status> {
+    info!("get_client_sme(id = {})", id);
+    let iface = ifaces.get(&id).ok_or(zx::Status::NOT_FOUND)?;
+    let result = iface.generic_sme.get_client_sme().await.map_err(|e| {
+        error!("Failed to request client SME: {}", e);
+        zx::Status::INTERNAL
+    })?;
+    result.map_err(|e| zx::Status::from_raw(e))
+}
+
+async fn get_ap_sme(
+    ifaces: &IfaceMap,
+    id: u16,
+) -> Result<fidl::endpoints::ClientEnd<fidl_sme::ApSmeMarker>, zx::Status> {
+    info!("get_ap_sme(id = {})", id);
+    let iface = ifaces.get(&id).ok_or(zx::Status::NOT_FOUND)?;
+    let result = iface.generic_sme.get_ap_sme().await.map_err(|e| {
+        error!("Failed to request AP SME: {}", e);
+        zx::Status::INTERNAL
+    })?;
+    result.map_err(|e| zx::Status::from_raw(e))
+}
+
+async fn get_sme_telemetry(
+    ifaces: &IfaceMap,
+    id: u16,
+) -> Result<fidl::endpoints::ClientEnd<fidl_sme::TelemetryMarker>, zx::Status> {
+    info!("get_sme_telemetry(id = {})", id);
+    let iface = ifaces.get(&id).ok_or(zx::Status::NOT_FOUND)?;
+    let result = iface.generic_sme.get_sme_telemetry().await.map_err(|e| {
+        error!("Failed to request SME telemetry: {}", e);
+        zx::Status::INTERNAL
+    })?;
+    result.map_err(|e| zx::Status::from_raw(e))
+}
+
 fn into_status_and_opt<T>(r: Result<T, zx::Status>) -> (zx::Status, Option<T>) {
     match r {
         Ok(x) => (zx::Status::OK, Some(x)),
@@ -278,7 +344,7 @@
     use {
         super::*,
         crate::device::PhyOwnership,
-        fidl::endpoints::create_proxy,
+        fidl::endpoints::{create_proxy, create_proxy_and_stream},
         fidl_fuchsia_wlan_common as fidl_wlan_common, fuchsia_async as fasync,
         futures::{future::BoxFuture, task::Poll, StreamExt},
         ieee80211::NULL_MAC_ADDR,
@@ -698,9 +764,14 @@
         pin_mut!(next_fut);
         assert_variant!(exec.run_until_stalled(&mut next_fut), Poll::Pending);
 
+        // Create a generic SME proxy but drop the server since we won't use it.
+        let (generic_sme, _) = create_proxy::<fidl_sme::GenericSmeMarker>()
+            .expect("Failed to create generic SME proxy");
         // Add an interface and make sure the update is received.
-        let fake_iface =
-            IfaceDevice { phy_ownership: PhyOwnership { phy_id: 0, phy_assigned_id: 0 } };
+        let fake_iface = IfaceDevice {
+            phy_ownership: PhyOwnership { phy_id: 0, phy_assigned_id: 0 },
+            generic_sme,
+        };
         test_values.ifaces.insert(0, fake_iface);
         assert_variant!(exec.run_until_stalled(&mut watcher_fut), Poll::Pending);
         assert_variant!(
@@ -735,9 +806,14 @@
 
         assert_variant!(exec.run_until_stalled(&mut service_fut), Poll::Pending);
 
+        // Create a generic SME proxy but drop the server since we won't use it.
+        let (generic_sme, _) = create_proxy::<fidl_sme::GenericSmeMarker>()
+            .expect("Failed to create generic SME proxy");
         // Add an interface before beginning to watch for devices.
-        let fake_iface =
-            IfaceDevice { phy_ownership: PhyOwnership { phy_id: 0, phy_assigned_id: 0 } };
+        let fake_iface = IfaceDevice {
+            phy_ownership: PhyOwnership { phy_id: 0, phy_assigned_id: 0 },
+            generic_sme,
+        };
         test_values.ifaces.insert(0, fake_iface);
         assert_variant!(exec.run_until_stalled(&mut watcher_fut), Poll::Pending);
 
@@ -1206,9 +1282,15 @@
     ) -> fidl_dev::PhyRequestStream {
         let (phy, phy_stream) = fake_phy();
         phy_map.insert(10, phy);
+        // Create a generic SME proxy but drop the server since we won't use it.
+        let (proxy, _) = create_proxy::<fidl_sme::GenericSmeMarker>()
+            .expect("Failed to create generic SME proxy");
         iface_map.insert(
             42,
-            device::IfaceDevice { phy_ownership: PhyOwnership { phy_id: 10, phy_assigned_id: 0 } },
+            device::IfaceDevice {
+                phy_ownership: PhyOwnership { phy_id: 10, phy_assigned_id: 0 },
+                generic_sme: proxy,
+            },
         );
         phy_stream
     }
@@ -1311,4 +1393,106 @@
         pin_mut!(fut);
         assert_eq!(Poll::Ready(Err(zx::Status::NOT_FOUND)), exec.run_until_stalled(&mut fut));
     }
+
+    #[test]
+    fn get_client_sme() {
+        let mut exec = fasync::TestExecutor::new().expect("Failed to create an executor");
+        let test_values = test_setup();
+        let (phy, _phy_stream) = fake_phy();
+        let phy_id = 10u16;
+        test_values.phys.insert(phy_id, phy);
+        let (generic_sme_proxy, mut generic_sme_stream) =
+            create_proxy_and_stream::<fidl_sme::GenericSmeMarker>()
+                .expect("Failed to create generic SME proxy and stream");
+
+        test_values.ifaces.insert(
+            42,
+            device::IfaceDevice {
+                phy_ownership: PhyOwnership { phy_id: 10, phy_assigned_id: 0 },
+                generic_sme: generic_sme_proxy,
+            },
+        );
+
+        let req_fut = super::get_client_sme(&test_values.ifaces, 42);
+        pin_mut!(req_fut);
+        assert_eq!(Poll::Pending, exec.run_until_stalled(&mut req_fut));
+
+        let (client_sme_client, client_sme_server) =
+            create_endpoints::<fidl_sme::ClientSmeMarker>().expect("Failed to create client SME");
+
+        // Respond to a client SME request with a client endpoint.
+        assert_variant!(exec.run_until_stalled(&mut generic_sme_stream.next()),
+            Poll::Ready(Some(Ok(fidl_sme::GenericSmeRequest::GetClientSme { responder }))) => {
+                responder.send(&mut Ok(client_sme_client)).expect("Failed to send response");
+            }
+        );
+        let client_sme = assert_variant!(exec.run_until_stalled(&mut req_fut), Poll::Ready(Ok(client_sme)) => client_sme);
+
+        // Verify that the correct endpoint was returned.
+        let client_sme_proxy = client_sme.into_proxy().expect("Failed to get client SME proxy");
+        let _status_req = client_sme_proxy.status();
+
+        let mut client_sme_stream =
+            client_sme_server.into_stream().expect("Failed to get client SME stream");
+        assert_variant!(
+            exec.run_until_stalled(&mut client_sme_stream.next()),
+            Poll::Ready(Some(Ok(fidl_sme::ClientSmeRequest::Status { .. })))
+        );
+    }
+
+    #[test]
+    fn get_client_sme_fails() {
+        let mut exec = fasync::TestExecutor::new().expect("Failed to create an executor");
+        let test_values = test_setup();
+        let (phy, _phy_stream) = fake_phy();
+        let phy_id = 10u16;
+        test_values.phys.insert(phy_id, phy);
+        let (generic_sme_proxy, generic_sme_server) = create_proxy::<fidl_sme::GenericSmeMarker>()
+            .expect("Failed to create generic SME proxy");
+
+        test_values.ifaces.insert(
+            42,
+            device::IfaceDevice {
+                phy_ownership: PhyOwnership { phy_id: 10, phy_assigned_id: 0 },
+                generic_sme: generic_sme_proxy,
+            },
+        );
+
+        let req_fut = super::get_client_sme(&test_values.ifaces, 42);
+        pin_mut!(req_fut);
+        assert_eq!(Poll::Pending, exec.run_until_stalled(&mut req_fut));
+
+        // Respond to a client SME request with an error.
+        let mut generic_sme_stream =
+            generic_sme_server.into_stream().expect("Failed to create generic SME stream");
+        assert_variant!(exec.run_until_stalled(&mut generic_sme_stream.next()),
+            Poll::Ready(Some(Ok(fidl_sme::GenericSmeRequest::GetClientSme { responder }))) => {
+                responder.send(&mut Err(1)).expect("Failed to send response");
+            }
+        );
+        assert_variant!(exec.run_until_stalled(&mut req_fut), Poll::Ready(Err(_)));
+    }
+
+    #[test]
+    fn get_client_sme_invalid_iface() {
+        let mut exec = fasync::TestExecutor::new().expect("Failed to create an executor");
+        let test_values = test_setup();
+        let (phy, _phy_stream) = fake_phy();
+        let phy_id = 10u16;
+        test_values.phys.insert(phy_id, phy);
+        let (generic_sme_proxy, _generic_sme_server) = create_proxy::<fidl_sme::GenericSmeMarker>()
+            .expect("Failed to create generic SME proxy");
+
+        test_values.ifaces.insert(
+            42,
+            device::IfaceDevice {
+                phy_ownership: PhyOwnership { phy_id: 10, phy_assigned_id: 0 },
+                generic_sme: generic_sme_proxy,
+            },
+        );
+
+        let req_fut = super::get_client_sme(&test_values.ifaces, 1337);
+        pin_mut!(req_fut);
+        assert_variant!(exec.run_until_stalled(&mut req_fut), Poll::Ready(Err(_)));
+    }
 }
diff --git a/src/connectivity/wlan/wlanstack/src/device.rs b/src/connectivity/wlan/wlanstack/src/device.rs
index f9e456b..9e854a9 100644
--- a/src/connectivity/wlan/wlanstack/src/device.rs
+++ b/src/connectivity/wlan/wlanstack/src/device.rs
@@ -5,6 +5,7 @@
 use {
     anyhow::{format_err, Error},
     fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_mlme as fidl_mlme,
+    fidl_fuchsia_wlan_sme as fidl_sme,
     fuchsia_inspect_contrib::{auto_persist, inspect_log},
     futures::{channel::mpsc, future::Future},
     log::info,
@@ -89,6 +90,7 @@
     spectrum_management_support: fidl_common::SpectrumManagementSupport,
     dev_monitor_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy,
     persistence_req_sender: auto_persist::PersistenceReqSender,
+    generic_sme: fidl::endpoints::ServerEnd<fidl_sme::GenericSmeMarker>,
 ) -> Result<impl Future<Output = Result<(), Error>>, Error> {
     let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
     let (sme, sme_fut) = create_sme(
@@ -102,6 +104,7 @@
         inspect_tree.hasher.clone(),
         persistence_req_sender,
         shutdown_receiver,
+        generic_sme,
     );
 
     info!("new iface #{} with role '{:?}'", id, device_info.role);
@@ -214,6 +217,8 @@
                 .expect("failed to create DeviceMonitor proxy");
         let (persistence_req_sender, _persistence_stream) =
             test_helper::create_inspect_persistence_channel();
+        let (generic_sme_proxy, generic_sme_server) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
 
         // Assert that the IfaceMap is initially empty.
         assert!(iface_map.get(&5).is_none());
@@ -232,6 +237,7 @@
             fake_spectrum_management_support_empty(),
             dev_monitor_proxy,
             persistence_req_sender,
+            generic_sme_server,
         )
         .expect("failed to create SME");
 
@@ -256,6 +262,7 @@
         pin_mut!(close_fut);
         let fut_result = exec.run_until_stalled(&mut close_fut);
         assert_variant!(fut_result, Poll::Ready(_), "expected closing SME to succeed");
+        drop(generic_sme_proxy);
 
         // Insert iface back into map.
         let (mlme_proxy, _) = create_proxy::<MlmeMarker>().expect("failed to create MlmeProxy");
@@ -303,6 +310,8 @@
             .expect("failed to create DeviceMonitor request stream");
         let (persistence_req_sender, _persistence_stream) =
             test_helper::create_inspect_persistence_channel();
+        let (_generic_sme_proxy, generic_sme_server) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
 
         // Assert that the IfaceMap is initially empty.
         assert!(iface_map.get(&5).is_none());
@@ -321,6 +330,7 @@
             fake_spectrum_management_support_empty(),
             dev_monitor_proxy,
             persistence_req_sender,
+            generic_sme_server,
         )
         .expect("failed to create SME");
 
diff --git a/src/connectivity/wlan/wlanstack/src/service.rs b/src/connectivity/wlan/wlanstack/src/service.rs
index 45ccd49..0d856b3 100644
--- a/src/connectivity/wlan/wlanstack/src/service.rs
+++ b/src/connectivity/wlan/wlanstack/src/service.rs
@@ -431,6 +431,7 @@
         spectrum_management_support,
         dev_monitor_proxy,
         persistence_req_sender,
+        req.generic_sme,
     ) {
         Ok(fut) => fut,
         Err(e) => return AddIfaceResult::from_error(e, zx::sys::ZX_ERR_INTERNAL),
@@ -964,13 +965,19 @@
         let cfg = ServiceCfg { wep_supported: false, wpa1_supported: false };
         let (persistence_req_sender, _persistence_stream) =
             test_helper::create_inspect_persistence_channel();
+        let (_generic_sme_proxy, generic_sme) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
 
         // Construct the request.
         let (mlme_channel, mlme_receiver) =
             create_endpoints().expect("failed to create fake MLME proxy");
         let mut mlme_stream = mlme_receiver.into_stream().expect("failed to create MLME stream");
-        let req =
-            fidl_svc::AddIfaceRequest { phy_id: 123, assigned_iface_id: 456, iface: mlme_channel };
+        let req = fidl_svc::AddIfaceRequest {
+            phy_id: 123,
+            assigned_iface_id: 456,
+            iface: mlme_channel,
+            generic_sme,
+        };
         let fut = add_iface(
             req,
             &cfg,
@@ -1041,6 +1048,8 @@
         let cfg = ServiceCfg { wep_supported: false, wpa1_supported: false };
         let (persistence_req_sender, _persistence_stream) =
             test_helper::create_inspect_persistence_channel();
+        let (_generic_sme_proxy, generic_sme) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
 
         // Construct the request.
         let (mlme_channel, mlme_receiver) =
@@ -1049,8 +1058,12 @@
         // Drop the receiver so that the initial device info query fails.
         drop(mlme_receiver);
 
-        let req =
-            fidl_svc::AddIfaceRequest { phy_id: 123, assigned_iface_id: 456, iface: mlme_channel };
+        let req = fidl_svc::AddIfaceRequest {
+            phy_id: 123,
+            assigned_iface_id: 456,
+            iface: mlme_channel,
+            generic_sme,
+        };
         let fut = add_iface(
             req,
             &cfg,
@@ -1084,14 +1097,20 @@
         let cfg = ServiceCfg { wep_supported: false, wpa1_supported: false };
         let (persistence_req_sender, _persistence_stream) =
             test_helper::create_inspect_persistence_channel();
+        let (_generic_sme_proxy, generic_sme) =
+            create_proxy::<fidl_sme::GenericSmeMarker>().expect("failed to create MlmeProxy");
 
         // Construct the request.
         let (mlme_channel, mlme_receiver) =
             create_endpoints().expect("failed to create fake MLME proxy");
         let mut mlme_stream = mlme_receiver.into_stream().expect("failed to create MLME stream");
 
-        let req =
-            fidl_svc::AddIfaceRequest { phy_id: 123, assigned_iface_id: 456, iface: mlme_channel };
+        let req = fidl_svc::AddIfaceRequest {
+            phy_id: 123,
+            assigned_iface_id: 456,
+            iface: mlme_channel,
+            generic_sme,
+        };
         let fut = add_iface(
             req,
             &cfg,