| // 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 super::*; |
| use crate::prelude::*; |
| use crate::spinel::*; |
| |
| use anyhow::Error; |
| use async_trait::async_trait; |
| use fasync::Time; |
| use fidl_fuchsia_lowpan::BeaconInfo; |
| use fidl_fuchsia_lowpan::*; |
| use fidl_fuchsia_lowpan_device::{ |
| DeviceState, EnergyScanParameters, NetworkScanParameters, ProvisioningMonitorMarker, |
| }; |
| use fidl_fuchsia_lowpan_test::*; |
| use fuchsia_async::TimeoutExt; |
| use futures::stream::BoxStream; |
| use futures::{StreamExt, TryFutureExt}; |
| use lowpan_driver_common::{AsyncConditionWait, Driver as LowpanDriver, FutureExt, ZxResult}; |
| use spinel_pack::{TryUnpack, EUI64}; |
| use std::convert::TryInto; |
| |
| /// Helpers for API-related tasks. |
| impl<DS: SpinelDeviceClient, NI: NetworkInterface> SpinelDriver<DS, NI> { |
| async fn set_scan_mask(&self, scan_mask: Option<&Vec<u16>>) -> Result<(), Error> { |
| if let Some(mask) = scan_mask { |
| let u8_mask = mask.iter().try_fold( |
| Vec::<u8>::new(), |
| |mut acc, &x| -> Result<Vec<u8>, Error> { |
| acc.push(TryInto::<u8>::try_into(x)?); |
| Ok(acc) |
| }, |
| )?; |
| |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::ScanMask.into(), u8_mask)) |
| .await? |
| } else { |
| self.frame_handler.send_request(CmdPropValueSet(PropMac::ScanMask.into(), ())).await? |
| } |
| Ok(()) |
| } |
| |
| fn start_generic_scan<'a, R, FInit, SStream>( |
| &'a self, |
| init_task: FInit, |
| stream: SStream, |
| ) -> BoxStream<'a, ZxResult<R>> |
| where |
| R: Send + 'a, |
| FInit: Send + Future<Output = Result<futures::lock::MutexGuard<'a, ()>, Error>> + 'a, |
| SStream: Send + Stream<Item = Result<R, Error>> + 'a, |
| { |
| enum InternalScanState<'a, R> { |
| Init( |
| crate::future::BoxFuture<'a, ZxResult<futures::lock::MutexGuard<'a, ()>>>, |
| BoxStream<'a, ZxResult<R>>, |
| ), |
| Running(futures::lock::MutexGuard<'a, ()>, BoxStream<'a, ZxResult<R>>), |
| Done, |
| } |
| |
| let init_task = init_task |
| .map_err(|e| ZxStatus::from(ErrorAdapter(e))) |
| .on_timeout(Time::after(DEFAULT_TIMEOUT), ncp_cmd_timeout!(self)); |
| |
| let stream = stream.map_err(|e| ZxStatus::from(ErrorAdapter(e))); |
| |
| futures::stream::unfold( |
| InternalScanState::Init(init_task.boxed(), stream.boxed()), |
| move |mut last_state: InternalScanState<'_, R>| async move { |
| last_state = match last_state { |
| InternalScanState::Init(init_task, stream) => { |
| fx_log_info!("generic_scan: initializing"); |
| match init_task.await { |
| Ok(lock) => InternalScanState::Running(lock, stream), |
| Err(err) => return Some((Err(err), InternalScanState::Done)), |
| } |
| } |
| last_state => last_state, |
| }; |
| |
| if let InternalScanState::Running(lock, mut stream) = last_state { |
| fx_log_info!("generic_scan: getting next"); |
| if let Some(next) = stream |
| .next() |
| .cancel_upon(self.ncp_did_reset.wait(), Some(Err(ZxStatus::CANCELED))) |
| .on_timeout(Time::after(DEFAULT_TIMEOUT), move || { |
| fx_log_err!("generic_scan: Timeout"); |
| self.ncp_is_misbehaving(); |
| Some(Err(ZxStatus::TIMED_OUT)) |
| }) |
| .await |
| { |
| return Some((next, InternalScanState::Running(lock, stream))); |
| } |
| } |
| |
| fx_log_info!("generic_scan: Done"); |
| |
| None |
| }, |
| ) |
| .boxed() |
| } |
| } |
| |
| /// API-related tasks. Implementation of [`lowpan_driver_common::Driver`]. |
| #[async_trait] |
| impl<DS: SpinelDeviceClient, NI: NetworkInterface> LowpanDriver for SpinelDriver<DS, NI> { |
| async fn provision_network(&self, params: ProvisioningParams) -> ZxResult<()> { |
| use std::convert::TryInto; |
| fx_log_info!("Got provision command: {:?}", params); |
| |
| if params.identity.raw_name.is_none() { |
| // We must at least have the network name specified. |
| Err(ZxStatus::INVALID_ARGS)?; |
| } |
| |
| let u8_channel: Option<u8> = if let Some(channel) = params.identity.channel { |
| Some(channel.try_into().map_err(|err| { |
| fx_log_err!("Error with channel value: {:?}", err); |
| ZxStatus::INVALID_ARGS |
| })?) |
| } else { |
| None |
| }; |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("provision_network").await?; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| let task = async { |
| // Bring down the mesh networking stack, if it is up. |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropNet::StackUp.into(), false).verify()) |
| .await?; |
| |
| // Bring down the interface, if it is up. |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropNet::InterfaceUp.into(), false).verify()) |
| .await?; |
| |
| // Make sure that any existing network state is cleared out. |
| self.frame_handler.send_request(CmdNetClear).await?; |
| |
| // From here down we are provisioning the NCP with the new network identity. |
| |
| // Set the network name. |
| if let Some(network_name) = params.identity.raw_name { |
| let network_name = std::str::from_utf8(&network_name)?.to_string(); |
| self.frame_handler |
| .send_request( |
| CmdPropValueSet(PropNet::NetworkName.into(), network_name).verify(), |
| ) |
| .await?; |
| } else { |
| // This code is unreachable because to verified that this |
| // field was populated in an earlier check. |
| unreachable!("Network name not set"); |
| } |
| |
| // Set the channel. |
| if let Some(channel) = u8_channel { |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropPhy::Chan.into(), channel).verify()) |
| .await?; |
| } else { |
| // In this case we are using whatever the previous channel was. |
| } |
| |
| // Set the XPANID, if we have one. |
| if let Some(xpanid) = params.identity.xpanid { |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropNet::Xpanid.into(), xpanid).verify()) |
| .await?; |
| } |
| |
| // Set the PANID, if we have one. |
| if let Some(panid) = params.identity.panid { |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::Panid.into(), panid).verify()) |
| .await?; |
| } |
| |
| // Set the credential, if we have one. |
| if let Some(fidl_fuchsia_lowpan::Credential::MasterKey(key)) = |
| params.credential.map(|x| *x) |
| { |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropNet::MasterKey.into(), key).verify()) |
| .await?; |
| } |
| |
| if self.driver_state.lock().has_cap(Cap::NetSave) { |
| // If we have the NetSave capability, go ahead and send the |
| // net save command. |
| self.frame_handler.send_request(CmdNetSave).await?; |
| } else { |
| // If we don't have the NetSave capability, we assume that |
| // it is saved automatically and tickle PropNet::Saved to |
| // make sure the other parts of the driver are aware. |
| self.on_prop_value_is(Prop::Net(PropNet::Saved), &[1u8])?; |
| } |
| |
| Ok(()) |
| }; |
| |
| self.apply_standard_combinators(task.boxed()).await |
| } |
| |
| async fn leave_network(&self) -> ZxResult<()> { |
| fx_log_info!("Got leave command"); |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("leave_network").await?; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| let task = async { |
| // Bring down the mesh networking stack, if it is up. |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropNet::StackUp.into(), false).verify()) |
| .await?; |
| |
| // Bring down the interface, if it is up. |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropNet::InterfaceUp.into(), false).verify()) |
| .await?; |
| |
| // Make sure that any existing network state is cleared out. |
| self.frame_handler.send_request(CmdNetClear).await?; |
| |
| Ok(()) |
| }; |
| |
| let ret = self.apply_standard_combinators(task.boxed()).await; |
| |
| // Clear the frame handler prepare for (re)initialization. |
| self.frame_handler.clear(); |
| self.driver_state.lock().prepare_for_init(); |
| self.driver_state_change.trigger(); |
| |
| ret |
| } |
| |
| async fn set_active(&self, enabled: bool) -> ZxResult<()> { |
| fx_log_info!("Got set active command: {:?}", enabled); |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("set_active").await?; |
| |
| // Wait until we are initialized, if we aren't already. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.apply_standard_combinators(self.net_if.set_enabled(enabled).boxed()).await?; |
| |
| Ok(()) |
| } |
| |
| async fn get_supported_network_types(&self) -> ZxResult<Vec<String>> { |
| fx_log_info!("Got get_supported_network_types command"); |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("get_supported_network_types").await?; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<InterfaceType, _>(Prop::InterfaceType) |
| .map_ok(|x| match x { |
| InterfaceType::ZigbeeIp => { |
| vec![fidl_fuchsia_lowpan::NET_TYPE_ZIGBEE_IP_1_X.to_string()] |
| } |
| InterfaceType::Thread => vec![fidl_fuchsia_lowpan::NET_TYPE_THREAD_1_X.to_string()], |
| _ => vec![], |
| }) |
| .await |
| } |
| |
| async fn get_supported_channels(&self) -> ZxResult<Vec<ChannelInfo>> { |
| use fidl_fuchsia_lowpan::ChannelInfo; |
| |
| fx_log_info!("Got get_supported_channels command"); |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("get_supported_channels").await?; |
| |
| traceln!("Got API task lock, waiting until we are ready."); |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| let results = self.get_property_simple::<Vec<u8>, _>(PropPhy::ChanSupported).await?; |
| |
| // TODO: Actually calculate all of the fields for channel info struct |
| |
| Ok(results |
| .into_iter() |
| .map(|x| ChannelInfo { |
| id: Some(x.to_string()), |
| index: Some(u16::from(x)), |
| masked_by_regulatory_domain: Some(false), |
| ..ChannelInfo::EMPTY |
| }) |
| .collect()) |
| } |
| |
| fn watch_device_state(&self) -> BoxStream<'_, ZxResult<DeviceState>> { |
| futures::stream::unfold( |
| None, |
| move |last_state: Option<(DeviceState, AsyncConditionWait<'_>)>| async move { |
| let mut snapshot; |
| if let Some((last_state, mut condition)) = last_state { |
| // The first item has already been emitted by the stream, so |
| // we need to wait for changes before we emit more. |
| loop { |
| // This loop is where our stream waits for |
| // the next change to the device state. |
| |
| // Wait for the driver state change condition to unblock. |
| condition.await; |
| |
| // Set up the condition for the next iteration. |
| condition = self.driver_state_change.wait(); |
| |
| snapshot = self.device_state_snapshot(); |
| if snapshot != last_state { |
| break; |
| } |
| } |
| |
| // We start out with our "delta" being a clone of the |
| // current device state. We will then selectively clear |
| // the fields it contains so that only fields that have |
| // changed are represented. |
| let mut delta = snapshot.clone(); |
| |
| if last_state.connectivity_state == snapshot.connectivity_state { |
| delta.connectivity_state = None; |
| } |
| |
| if last_state.role == snapshot.role { |
| delta.role = None; |
| } |
| |
| Some((Ok(delta), Some((snapshot, condition)))) |
| } else { |
| // This is the first item being emitted from the stream, |
| // so we end up emitting the current device state and |
| // setting ourselves up for the next iteration. |
| let condition = self.driver_state_change.wait(); |
| snapshot = self.device_state_snapshot(); |
| Some((Ok(snapshot.clone()), Some((snapshot, condition)))) |
| } |
| }, |
| ) |
| .boxed() |
| } |
| |
| fn watch_identity(&self) -> BoxStream<'_, ZxResult<Identity>> { |
| futures::stream::unfold( |
| None, |
| move |last_state: Option<(Identity, AsyncConditionWait<'_>)>| async move { |
| let mut snapshot; |
| if let Some((last_state, mut condition)) = last_state { |
| // The first copy of the identity has already been emitted |
| // by the stream, so we need to wait for changes before we emit more. |
| loop { |
| // This loop is where our stream waits for |
| // the next change to the identity. |
| |
| // Wait for the driver state change condition to unblock. |
| condition.await; |
| |
| // Set up the condition for the next iteration. |
| condition = self.driver_state_change.wait(); |
| |
| // Grab our identity snapshot and make sure it is actually different. |
| snapshot = self.identity_snapshot(); |
| if snapshot != last_state { |
| break; |
| } |
| } |
| Some((Ok(snapshot.clone()), Some((snapshot, condition)))) |
| } else { |
| // This is the first item being emitted from the stream, |
| // so we end up emitting the current identity and |
| // setting ourselves up for the next iteration. |
| let condition = self.driver_state_change.wait(); |
| snapshot = self.identity_snapshot(); |
| Some((Ok(snapshot.clone()), Some((snapshot, condition)))) |
| } |
| }, |
| ) |
| .boxed() |
| } |
| |
| async fn form_network( |
| &self, |
| params: ProvisioningParams, |
| progress: fidl::endpoints::ServerEnd<ProvisioningMonitorMarker>, |
| ) { |
| fx_log_info!("Got form command: {:?}", params); |
| |
| // Wait for our turn. |
| let _lock = match self.wait_for_api_task_lock("form_network").await { |
| Ok(x) => x, |
| Err(x) => { |
| fx_log_info!("Failed waiting for API task lock: {:?}", x); |
| return; |
| } |
| }; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // TODO: Implement form_network() |
| // We don't care about errors here because |
| // we are simply reporting that this isn't implemented. |
| let _ = progress.close_with_epitaph(ZxStatus::NOT_SUPPORTED); |
| } |
| |
| async fn join_network( |
| &self, |
| params: ProvisioningParams, |
| progress: fidl::endpoints::ServerEnd<ProvisioningMonitorMarker>, |
| ) { |
| fx_log_info!("Got join command: {:?}", params); |
| |
| // Wait for our turn. |
| let _lock = match self.wait_for_api_task_lock("join_network").await { |
| Ok(x) => x, |
| Err(x) => { |
| fx_log_info!("Failed waiting for API task lock: {:?}", x); |
| return; |
| } |
| }; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // TODO: Implement join_network() |
| // We don't care about errors here because |
| // we are simply reporting that this isn't implemented. |
| let _ = progress.close_with_epitaph(ZxStatus::NOT_SUPPORTED); |
| } |
| |
| async fn get_credential(&self) -> ZxResult<Option<fidl_fuchsia_lowpan::Credential>> { |
| fx_log_info!("Got get credential command"); |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| if self.driver_state.lock().is_ready() { |
| self.get_property_simple::<Vec<u8>, _>(PropNet::MasterKey) |
| .and_then(|key| { |
| futures::future::ready(if key.is_empty() { |
| Ok(None) |
| } else { |
| Ok(Some(fidl_fuchsia_lowpan::Credential::MasterKey(key))) |
| }) |
| }) |
| .await |
| } else { |
| Ok(None) |
| } |
| } |
| |
| fn start_energy_scan( |
| &self, |
| params: &EnergyScanParameters, |
| ) -> BoxStream<'_, ZxResult<Vec<fidl_fuchsia_lowpan_device::EnergyScanResult>>> { |
| fx_log_info!("Got energy scan command: {:?}", params); |
| |
| let channels = params.channels.clone(); |
| let dwell_time = params.dwell_time_ms; |
| |
| let init_task = async move { |
| // Wait for our turn. |
| let lock = self.wait_for_api_task_lock("start_energy_scan").await?; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // Set the channel mask. |
| self.set_scan_mask(channels.as_ref()).await?; |
| |
| // Set dwell time. |
| if let Some(dwell_time) = dwell_time { |
| self.frame_handler |
| .send_request(CmdPropValueSet( |
| PropMac::ScanPeriod.into(), |
| TryInto::<u16>::try_into(dwell_time)?, |
| )) |
| .await? |
| } else { |
| self.frame_handler |
| .send_request(CmdPropValueSet( |
| PropMac::ScanPeriod.into(), |
| DEFAULT_SCAN_DWELL_TIME_MS, |
| )) |
| .await? |
| } |
| |
| // Start the scan. |
| self.frame_handler |
| .send_request( |
| CmdPropValueSet(PropMac::ScanState.into(), ScanState::Energy).verify(), |
| ) |
| .await?; |
| |
| traceln!("energy_scan: Scan started!"); |
| |
| Ok(lock) |
| }; |
| |
| let stream = self.frame_handler.inspect_as_stream(|frame| { |
| traceln!("energy_scan: Inspecting {:?}", frame); |
| if frame.cmd == Cmd::PropValueInserted { |
| match SpinelPropValueRef::try_unpack_from_slice(frame.payload) |
| .context("energy_scan") |
| { |
| Ok(prop_value) if prop_value.prop == Prop::Mac(PropMac::EnergyScanResult) => { |
| let mut iter = prop_value.value.iter(); |
| let result = match EnergyScanResult::try_unpack(&mut iter) { |
| Ok(val) => val, |
| Err(err) => return Some(Err(err)), |
| }; |
| fx_log_info!("energy_scan: got result: {:?}", result); |
| |
| Some(Ok(Some(vec![fidl_fuchsia_lowpan_device::EnergyScanResult { |
| channel_index: Some(result.channel as u16), |
| max_rssi: Some(result.rssi as i32), |
| ..fidl_fuchsia_lowpan_device::EnergyScanResult::EMPTY |
| }]))) |
| } |
| Err(err) => Some(Err(err)), |
| _ => None, |
| } |
| } else if frame.cmd == Cmd::PropValueIs { |
| match SpinelPropValueRef::try_unpack_from_slice(frame.payload) |
| .context("energy_scan") |
| { |
| Ok(prop_value) if prop_value.prop == Prop::Mac(PropMac::ScanState) => { |
| let mut iter = prop_value.value.iter(); |
| if Some(ScanState::Energy) != ScanState::try_unpack(&mut iter).ok() { |
| fx_log_info!("energy_scan: scan ended"); |
| Some(Ok(None)) |
| } else { |
| None |
| } |
| } |
| Err(err) => Some(Err(err)), |
| _ => None, |
| } |
| } else { |
| None |
| } |
| }); |
| |
| self.start_generic_scan(init_task, stream) |
| } |
| |
| fn start_network_scan( |
| &self, |
| params: &NetworkScanParameters, |
| ) -> BoxStream<'_, ZxResult<Vec<BeaconInfo>>> { |
| fx_log_info!("Got network scan command: {:?}", params); |
| |
| let channels = params.channels.clone(); |
| let tx_power = params.tx_power_dbm; |
| |
| let init_task = async move { |
| // Wait for our turn. |
| let lock = self.wait_for_api_task_lock("start_network_scan").await?; |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // Set the channel mask. |
| self.set_scan_mask(channels.as_ref()).await?; |
| |
| // Set beacon request transmit power |
| if let Some(tx_power) = tx_power { |
| // Saturate to signed 8-bit integer |
| let tx_power = if tx_power > i8::MAX as i32 { |
| i8::MAX as i32 |
| } else if tx_power < i8::MIN as i32 { |
| i8::MIN as i32 |
| } else { |
| tx_power |
| }; |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropPhy::TxPower.into(), tx_power)) |
| .await? |
| } |
| |
| // Start the scan. |
| self.frame_handler |
| .send_request( |
| CmdPropValueSet(PropMac::ScanState.into(), ScanState::Beacon).verify(), |
| ) |
| .await?; |
| |
| Ok(lock) |
| }; |
| |
| let stream = self.frame_handler.inspect_as_stream(|frame| { |
| if frame.cmd == Cmd::PropValueInserted { |
| match SpinelPropValueRef::try_unpack_from_slice(frame.payload) |
| .context("network_scan") |
| { |
| Ok(prop_value) if prop_value.prop == Prop::Mac(PropMac::ScanBeacon) => { |
| let mut iter = prop_value.value.iter(); |
| let result = match NetScanResult::try_unpack(&mut iter) { |
| Ok(val) => val, |
| Err(err) => { |
| // There was an error parsing the scan result. |
| // We don't treat this as fatal, we just skip this entry. |
| // We do print out the error, though. |
| fx_log_warn!( |
| "Unable to parse network scan result: {:?} ({:x?})", |
| err, |
| prop_value.value |
| ); |
| return None; |
| } |
| }; |
| |
| fx_log_info!("network_scan: got result: {:?}", result); |
| |
| Some(Ok(Some(vec![BeaconInfo { |
| identity: Identity { |
| raw_name: Some(result.net.network_name), |
| channel: Some(result.channel as u16), |
| panid: Some(result.mac.panid), |
| xpanid: Some(result.net.xpanid), |
| ..Identity::EMPTY |
| }, |
| rssi: result.rssi as i32, |
| lqi: result.mac.lqi, |
| address: result.mac.long_addr.0.to_vec(), |
| flags: vec![], |
| }]))) |
| } |
| Err(err) => Some(Err(err)), |
| _ => None, |
| } |
| } else if frame.cmd == Cmd::PropValueIs { |
| match SpinelPropValueRef::try_unpack_from_slice(frame.payload) |
| .context("network_scan") |
| { |
| Ok(prop_value) if prop_value.prop == Prop::Mac(PropMac::ScanState) => { |
| let mut iter = prop_value.value.iter(); |
| if Some(ScanState::Beacon) != ScanState::try_unpack(&mut iter).ok() { |
| fx_log_info!("network_scan: scan ended"); |
| Some(Ok(None)) |
| } else { |
| None |
| } |
| } |
| Err(err) => Some(Err(err)), |
| _ => None, |
| } |
| } else { |
| None |
| } |
| }); |
| |
| self.start_generic_scan(init_task, stream) |
| } |
| |
| async fn reset(&self) -> ZxResult<()> { |
| fx_log_info!("Got reset command"); |
| |
| // Cancel everyone with an outstanding command. |
| self.frame_handler.clear(); |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("reset").await?; |
| |
| // Clear the frame handler one more time and prepare for (re)initialization. |
| self.frame_handler.clear(); |
| self.driver_state.lock().prepare_for_init(); |
| self.driver_state_change.trigger(); |
| |
| // Wait for initialization to complete. |
| // The reset will happen during initialization. |
| fx_log_info!("reset: Waiting for driver to finish initializing"); |
| self.wait_for_state(DriverState::is_initialized) |
| .boxed() |
| .map(|_| Ok(())) |
| .on_timeout(Time::after(DEFAULT_TIMEOUT), ncp_cmd_timeout!(self)) |
| .await |
| } |
| |
| async fn get_factory_mac_address(&self) -> ZxResult<Vec<u8>> { |
| fx_log_info!("Got get_factory_mac_address command"); |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<Vec<u8>, _>(Prop::HwAddr).await |
| } |
| |
| async fn get_current_mac_address(&self) -> ZxResult<Vec<u8>> { |
| fx_log_info!("Got get_current_mac_address command"); |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<Vec<u8>, _>(PropMac::LongAddr).await |
| } |
| |
| async fn get_ncp_version(&self) -> ZxResult<String> { |
| fx_log_info!("Got get_ncp_version command"); |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<String, _>(Prop::NcpVersion).await |
| } |
| |
| async fn get_current_channel(&self) -> ZxResult<u16> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<u8, _>(PropPhy::Chan).map_ok(|x| x as u16).await |
| } |
| |
| // Returns the current RSSI measured by the radio. |
| // <fxbug.dev/44668> |
| async fn get_current_rssi(&self) -> ZxResult<i32> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<i8, _>(PropPhy::Rssi).map_ok(|x| x as i32).await |
| } |
| |
| async fn get_partition_id(&self) -> ZxResult<u32> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<u32, _>(PropNet::PartitionId).await |
| } |
| |
| async fn get_thread_rloc16(&self) -> ZxResult<u16> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.get_property_simple::<u16, _>(PropThread::Rloc16).await |
| } |
| |
| async fn get_thread_router_id(&self) -> ZxResult<u8> { |
| Err(ZxStatus::NOT_SUPPORTED) |
| } |
| |
| async fn send_mfg_command(&self, command: &str) -> ZxResult<String> { |
| fx_log_info!("Got send_mfg_command"); |
| |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| self.apply_standard_combinators( |
| self.frame_handler |
| .send_request( |
| CmdPropValueSet(PropStream::Mfg.into(), command.to_string()) |
| .returning::<String>(), |
| ) |
| .boxed(), |
| ) |
| .await |
| } |
| |
| async fn commission_network( |
| &self, |
| _secret: &[u8], |
| _progress: fidl::endpoints::ServerEnd<ProvisioningMonitorMarker>, |
| ) { |
| fx_log_info!("Got commission command"); |
| } |
| |
| async fn replace_mac_address_filter_settings( |
| &self, |
| settings: MacAddressFilterSettings, |
| ) -> ZxResult<()> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("get_mac_address_filter_settings").await?; |
| |
| // Disbale allowlist/denylist |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::AllowListEnabled.into(), false).verify()) |
| .await |
| .map_err(|err| { |
| fx_log_err!("Error disable allowlist: {}", err); |
| ZxStatus::INTERNAL |
| })?; |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::DenyListEnabled.into(), false).verify()) |
| .await |
| .map_err(|err| { |
| fx_log_err!("Error disbale denylist: {}", err); |
| ZxStatus::INTERNAL |
| })?; |
| |
| match settings.mode { |
| Some(MacAddressFilterMode::Disabled) => Ok(()), |
| Some(MacAddressFilterMode::Allow) => { |
| let allow_list = settings |
| .items |
| .or(Some(vec![])) |
| .unwrap() |
| .into_iter() |
| .map(|x| { |
| Ok(AllowListEntry { |
| mac_addr: EUI64( |
| x.mac_address |
| .unwrap_or(vec![]) |
| .try_into() |
| .map_err(|_| Err(ZxStatus::INVALID_ARGS))?, |
| ), |
| rssi: x.rssi.unwrap_or(127), |
| }) |
| }) |
| .collect::<Result<Vec<AllowListEntry>, ZxStatus>>()?; |
| // Set allowlist |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::AllowList.into(), allow_list).verify()) |
| .await |
| .map_err(|err| { |
| fx_log_err!("Error with CmdPropValueSet: {:?}", err); |
| ZxStatus::INTERNAL |
| })?; |
| // Enable allowlist |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::AllowListEnabled.into(), true).verify()) |
| .await |
| .map_err(|err| { |
| fx_log_err!("Error enable allowlist: {}", err); |
| ZxStatus::INTERNAL |
| }) |
| } |
| Some(MacAddressFilterMode::Deny) => { |
| // Construct denylist |
| let deny_list = settings |
| .items |
| .or(Some(vec![])) |
| .unwrap() |
| .into_iter() |
| .map(|x| { |
| x.mac_address.map_or(Err(ZxStatus::INVALID_ARGS), Ok).and_then(|x| { |
| // TODO (jiamingw): apply this change after the openthread library patch is in |
| // Ok(DenyListEntry { |
| // mac_addr: EUI64( |
| // x.try_into().map_err(|_| Err(ZxStatus::INVALID_ARGS))?, |
| // ), |
| // }) |
| Ok(AllowListEntry { |
| mac_addr: EUI64( |
| x.try_into().map_err(|_| Err(ZxStatus::INVALID_ARGS))?, |
| ), |
| rssi: 127, |
| }) |
| }) |
| }) |
| .collect::<Result<Vec<AllowListEntry>, ZxStatus>>()?; |
| // Set denylist |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::AllowList.into(), deny_list).verify()) |
| .await |
| .map_err(|err| { |
| fx_log_err!("Error with CmdPropValueSet: {:?}", err); |
| ZxStatus::INTERNAL |
| })?; |
| // Enable denylist |
| self.frame_handler |
| .send_request(CmdPropValueSet(PropMac::DenyListEnabled.into(), true).verify()) |
| .await |
| .map_err(|err| { |
| fx_log_err!("Error enable denylist: {}", err); |
| ZxStatus::INTERNAL |
| }) |
| } |
| _ => Err(ZxStatus::NOT_SUPPORTED), |
| } |
| } |
| |
| async fn get_mac_address_filter_settings(&self) -> ZxResult<MacAddressFilterSettings> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("get_mac_address_filter_settings").await?; |
| |
| let allow_list_enabled = |
| self.get_property_simple::<bool, _>(PropMac::AllowListEnabled).await?; |
| |
| let deny_list_enabled = |
| self.get_property_simple::<bool, _>(PropMac::DenyListEnabled).await?; |
| |
| if allow_list_enabled && deny_list_enabled { |
| fx_log_err!("get_mac_address_filter_settings: Both allow and deny list are enabled"); |
| self.ncp_is_misbehaving(); |
| return Err(ZxStatus::INTERNAL); |
| } |
| |
| let mode = if allow_list_enabled == true { |
| MacAddressFilterMode::Allow |
| } else if deny_list_enabled == true { |
| MacAddressFilterMode::Deny |
| } else { |
| MacAddressFilterMode::Disabled |
| }; |
| |
| let filter_item_vec = match mode { |
| MacAddressFilterMode::Allow => self |
| .get_property_simple::<AllowList, _>(PropMac::AllowList) |
| .await? |
| .into_iter() |
| .map(|item| MacAddressFilterItem { |
| mac_address: Some(item.mac_addr.0.to_vec()), |
| rssi: Some(item.rssi), |
| ..MacAddressFilterItem::EMPTY |
| }) |
| .collect::<Vec<_>>(), |
| MacAddressFilterMode::Deny => self |
| .get_property_simple::<DenyList, _>(PropMac::DenyList) |
| .await? |
| .into_iter() |
| .map(|item| MacAddressFilterItem { |
| mac_address: Some(item.mac_addr.0.to_vec()), |
| rssi: None, |
| ..MacAddressFilterItem::EMPTY |
| }) |
| .collect::<Vec<_>>(), |
| _ => vec![], |
| }; |
| |
| match mode { |
| MacAddressFilterMode::Disabled => Ok(MacAddressFilterSettings { |
| mode: Some(MacAddressFilterMode::Disabled), |
| ..MacAddressFilterSettings::EMPTY |
| }), |
| MacAddressFilterMode::Allow => Ok(MacAddressFilterSettings { |
| mode: Some(MacAddressFilterMode::Allow), |
| items: Some(filter_item_vec), |
| ..MacAddressFilterSettings::EMPTY |
| }), |
| MacAddressFilterMode::Deny => Ok(MacAddressFilterSettings { |
| mode: Some(MacAddressFilterMode::Deny), |
| items: Some(filter_item_vec), |
| ..MacAddressFilterSettings::EMPTY |
| }), |
| } |
| } |
| |
| async fn get_neighbor_table(&self) -> ZxResult<Vec<NeighborInfo>> { |
| // Wait until we are ready. |
| self.wait_for_state(DriverState::is_initialized).await; |
| |
| // Wait for our turn. |
| let _lock = self.wait_for_api_task_lock("get_neighbor_table").await?; |
| |
| Ok(self |
| .get_property_simple::<NeighborTable, _>(PropThread::NeighborTable) |
| .await? |
| .into_iter() |
| .map(|item| NeighborInfo { |
| mac_address: Some(item.extended_addr.0.to_vec()), |
| short_address: Some(item.short_addr), |
| age: Some(fuchsia_zircon::Duration::from_seconds(item.age.into()).into_nanos()), |
| is_child: Some(item.is_child), |
| link_frame_count: Some(item.link_frame_cnt), |
| mgmt_frame_count: Some(item.mle_frame_cnt), |
| last_rssi_in: Some(item.last_rssi.into()), |
| avg_rssi_in: Some(item.avg_rssi), |
| lqi_in: Some(item.link_quality), |
| thread_mode: Some(item.mode), |
| ..NeighborInfo::EMPTY |
| }) |
| .collect::<Vec<_>>()) |
| } |
| |
| async fn get_counters(&self) -> ZxResult<Counters> { |
| // TODO: Implement. |
| return Ok(Counters::EMPTY); |
| } |
| |
| async fn reset_counters(&self) -> ZxResult<Counters> { |
| // TODO: Implement. |
| return Ok(Counters::EMPTY); |
| } |
| } |
| |
| impl<DS: SpinelDeviceClient, NI: NetworkInterface> SpinelDriver<DS, NI> { |
| fn device_state_snapshot(&self) -> DeviceState { |
| let driver_state = self.driver_state.lock(); |
| DeviceState { |
| connectivity_state: Some(driver_state.connectivity_state), |
| role: Some(driver_state.role), |
| ..DeviceState::EMPTY |
| } |
| } |
| |
| fn identity_snapshot(&self) -> Identity { |
| let driver_state = self.driver_state.lock(); |
| driver_state.identity.clone() |
| } |
| } |