use core::fmt::{self, Debug};
use core::future::{ready, Future};
use crate::dm::clusters::gen_comm::GenCommHandler;
use crate::dm::networks::wireless::{Thread, ThreadTLV, MAX_WIRELESS_NETWORK_ID_LEN};
use crate::dm::networks::NetChangeNotif;
use crate::dm::{ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext, WriteContext};
use crate::error::{Error, ErrorCode};
use crate::persist::{Persist, NETWORKS_KEY};
use crate::tlv::{
Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilder, TLVBuilderParent, TLVWrite,
ToTLVArrayBuilder, ToTLVBuilder,
};
use crate::utils::cell::RefCell;
use crate::utils::init::{init, Init};
use crate::utils::sync::blocking::Mutex;
use crate::utils::sync::{DynBase, Notification};
use crate::with;
pub use crate::dm::clusters::decl::network_commissioning::*;
pub use crate::dm::clusters::groups;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NetworkType {
Ethernet,
Wifi,
Thread,
}
impl NetworkType {
pub const fn cluster(&self) -> Cluster<'static> {
match self {
Self::Ethernet => FULL_CLUSTER
.with_features(Feature::ETHERNET_NETWORK_INTERFACE.bits())
.with_attrs(with!(required))
.with_cmds(with!()),
Self::Wifi => FULL_CLUSTER
.with_features(Feature::WI_FI_NETWORK_INTERFACE.bits())
.with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::SupportedWiFiBands))
.with_cmds(with!(CommandId::AddOrUpdateWiFiNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)),
Self::Thread => FULL_CLUSTER
.with_features(Feature::THREAD_NETWORK_INTERFACE.bits())
.with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::ThreadVersion | AttributeId::SupportedThreadFeatures))
.with_cmds(with!(CommandId::AddOrUpdateThreadNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NetworkInfo<'a> {
pub network_id: &'a [u8],
pub connected: bool,
}
impl NetworkInfo<'_> {
fn read_into<P: TLVBuilderParent>(
&self,
builder: NetworkInfoStructBuilder<P>,
) -> Result<P, Error> {
builder
.network_id(Octets::new(self.network_id))?
.connected(self.connected)?
.network_identifier(None)?
.client_identifier(None)?
.end()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NetworkScanInfo<'a> {
Wifi {
security: WiFiSecurityBitmap,
ssid: &'a [u8],
bssid: &'a [u8],
channel: u16,
band: WiFiBandEnum,
rssi: i8,
},
Thread {
pan_id: u16,
ext_pan_id: u64,
network_name: &'a str,
channel: u16,
version: u8,
ext_addr: &'a [u8],
rssi: i8,
lqi: u8,
},
}
impl NetworkScanInfo<'_> {
pub fn wifi_read_into<P: TLVBuilderParent>(
&self,
builder: WiFiInterfaceScanResultStructBuilder<P>,
) -> Result<P, Error> {
let NetworkScanInfo::Wifi {
security,
ssid,
bssid,
channel,
band,
rssi,
} = self
else {
panic!("Wifi scan info expected");
};
builder
.security(*security)?
.ssid(Octets::new(ssid))?
.bssid(Octets::new(bssid))?
.channel(*channel)?
.wi_fi_band(*band)?
.rssi(*rssi)?
.end()
}
pub fn thread_read_into<P: TLVBuilderParent>(
&self,
builder: ThreadInterfaceScanResultStructBuilder<P>,
) -> Result<P, Error> {
let NetworkScanInfo::Thread {
pan_id,
ext_pan_id: extended_pan_id,
network_name,
channel,
version,
ext_addr,
rssi,
lqi,
} = self
else {
panic!("Thread scan info expected");
};
builder
.pan_id(*pan_id)?
.extended_pan_id(*extended_pan_id)?
.network_name(network_name)?
.channel(*channel)?
.version(*version)?
.extended_address(Octets::new(ext_addr))?
.rssi(*rssi)?
.lqi(*lqi)?
.end()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum WirelessCreds<'a> {
Wifi { ssid: &'a [u8], pass: &'a [u8] },
Thread { dataset_tlv: &'a [u8] },
}
impl WirelessCreds<'_> {
pub fn id(&self) -> Result<&[u8], Error> {
match self {
WirelessCreds::Wifi { ssid, .. } => Ok(ssid),
WirelessCreds::Thread { dataset_tlv } => Thread::dataset_ext_pan_id(dataset_tlv),
}
}
pub fn check_match(&self, net_type: NetworkType) -> Result<(), Error> {
match self {
WirelessCreds::Wifi { .. } if matches!(net_type, NetworkType::Wifi) => Ok(()),
WirelessCreds::Thread { .. } if matches!(net_type, NetworkType::Thread) => Ok(()),
_ => Err(ErrorCode::InvalidAction.into()),
}
}
}
impl fmt::Display for WirelessCreds<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WirelessCreds::Wifi { ssid, .. } => write!(
f,
"SSID({})",
core::str::from_utf8(ssid).ok().unwrap_or("???")
),
WirelessCreds::Thread { dataset_tlv } => write!(
f,
"ExtEpanId({:?})",
ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[])
),
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for WirelessCreds<'_> {
fn format(&self, fmt: defmt::Formatter) {
match self {
WirelessCreds::Wifi { ssid, .. } => defmt::write!(
fmt,
"SSID({})",
core::str::from_utf8(ssid).ok().unwrap_or("???")
),
WirelessCreds::Thread { dataset_tlv } => defmt::write!(
fmt,
"ExtEpanId({:?})",
ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[])
),
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NetworksError {
NetworkIdNotFound,
DuplicateNetworkId,
OutOfRange,
BoundsExceeded,
Other(Error),
}
impl From<Error> for NetworksError {
fn from(err: Error) -> Self {
NetworksError::Other(err)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NetCtlError {
NetworkNotFound,
UnsupportedSecurity,
AuthFailure,
OtherConnectionFailure,
IpBindFailed,
IpV6Failed,
Other(Error),
}
impl From<Error> for NetCtlError {
fn from(err: Error) -> Self {
NetCtlError::Other(err)
}
}
impl NetworkCommissioningStatusEnum {
pub fn map<T>(
result: Result<T, NetworksError>,
) -> Result<(NetworkCommissioningStatusEnum, Option<i32>, Option<T>), Error> {
if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_status(&result) {
Ok((status, err_code, result.ok()))
} else {
match result {
Err(NetworksError::Other(e)) => Err(e),
_ => unreachable!(),
}
}
}
pub fn map_status<T>(
result: &Result<T, NetworksError>,
) -> Option<(NetworkCommissioningStatusEnum, Option<i32>)> {
match result {
Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)),
Err(NetworksError::NetworkIdNotFound) => {
Some((NetworkCommissioningStatusEnum::NetworkIDNotFound, None))
}
Err(NetworksError::DuplicateNetworkId) => {
Some((NetworkCommissioningStatusEnum::DuplicateNetworkID, None))
}
Err(NetworksError::OutOfRange) => {
Some((NetworkCommissioningStatusEnum::OutOfRange, None))
}
Err(NetworksError::BoundsExceeded) => {
Some((NetworkCommissioningStatusEnum::BoundsExceeded, None))
}
Err(NetworksError::Other(_)) => None,
}
}
pub fn map_ctl<T>(
result: Result<T, NetCtlError>,
) -> Result<(NetworkCommissioningStatusEnum, Option<i32>, Option<T>), Error> {
if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) {
Ok((status, err_code, result.ok()))
} else {
match result {
Err(NetCtlError::Other(e)) => Err(e),
_ => unreachable!(),
}
}
}
pub fn map_ctl_status<T>(
result: &Result<T, NetCtlError>,
) -> Option<(NetworkCommissioningStatusEnum, Option<i32>)> {
match result {
Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)),
Err(NetCtlError::UnsupportedSecurity) => {
Some((NetworkCommissioningStatusEnum::UnsupportedSecurity, None))
}
Err(NetCtlError::AuthFailure) => {
Some((NetworkCommissioningStatusEnum::AuthFailure, None))
}
Err(NetCtlError::IpBindFailed) => {
Some((NetworkCommissioningStatusEnum::IPBindFailed, None))
}
Err(NetCtlError::IpV6Failed) => {
Some((NetworkCommissioningStatusEnum::IPV6Failed, None))
}
Err(NetCtlError::OtherConnectionFailure) => {
Some((NetworkCommissioningStatusEnum::OtherConnectionFailure, None))
}
Err(NetCtlError::NetworkNotFound) => {
Some((NetworkCommissioningStatusEnum::NetworkNotFound, None))
}
Err(NetCtlError::Other(_)) => None,
}
}
pub fn read_into<P: TLVBuilderParent>(
&self,
index: Option<u8>,
builder: NetworkConfigResponseBuilder<P>,
) -> Result<P, Error> {
builder
.networking_status(*self)?
.debug_text(None)?
.network_index(index)?
.client_identity(None)?
.possession_signature(None)?
.end()
}
}
pub trait Networks {
fn max_networks(&self) -> Result<u8, Error>;
fn networks(&self, f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error>;
fn creds(
&self,
network_id: &[u8],
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<u8, NetworksError>;
fn next_creds(
&self,
last_network_id: Option<&[u8]>,
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<bool, Error>;
fn enabled(&self) -> Result<bool, Error>;
fn set_enabled(&mut self, enabled: bool) -> Result<(), Error>;
fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError>;
fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError>;
fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError>;
fn commissioned(&self) -> Result<bool, Error>;
fn set_commissioned(&mut self, commissioned: bool) -> Result<(), Error>;
fn reset(&mut self) -> Result<(), Error>;
fn load(&mut self, data: &[u8]) -> Result<(), Error>;
fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error>;
}
impl<T> Networks for &mut T
where
T: Networks,
{
fn max_networks(&self) -> Result<u8, Error> {
(**self).max_networks()
}
fn networks(&self, f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error> {
(**self).networks(f)
}
fn creds(
&self,
network_id: &[u8],
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<u8, NetworksError> {
(**self).creds(network_id, f)
}
fn next_creds(
&self,
last_network_id: Option<&[u8]>,
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<bool, Error> {
(**self).next_creds(last_network_id, f)
}
fn enabled(&self) -> Result<bool, Error> {
(**self).enabled()
}
fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
(*self).set_enabled(enabled)
}
fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
(*self).add_or_update(creds)
}
fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
(*self).reorder(index, network_id)
}
fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
(*self).remove(network_id)
}
fn commissioned(&self) -> Result<bool, Error> {
(**self).commissioned()
}
fn set_commissioned(&mut self, commissioned: bool) -> Result<(), Error> {
(**self).set_commissioned(commissioned)
}
fn reset(&mut self) -> Result<(), Error> {
(**self).reset()
}
fn load(&mut self, data: &[u8]) -> Result<(), Error> {
(**self).load(data)
}
fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
(**self).save(buf)
}
}
impl Networks for &mut dyn Networks {
fn max_networks(&self) -> Result<u8, Error> {
(**self).max_networks()
}
fn networks(&self, f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error> {
(**self).networks(f)
}
fn creds(
&self,
network_id: &[u8],
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<u8, NetworksError> {
(**self).creds(network_id, f)
}
fn next_creds(
&self,
last_network_id: Option<&[u8]>,
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<bool, Error> {
(**self).next_creds(last_network_id, f)
}
fn enabled(&self) -> Result<bool, Error> {
(**self).enabled()
}
fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
(**self).set_enabled(enabled)
}
fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
(**self).add_or_update(creds)
}
fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
(**self).reorder(index, network_id)
}
fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
(**self).remove(network_id)
}
fn commissioned(&self) -> Result<bool, Error> {
(**self).commissioned()
}
fn set_commissioned(&mut self, commissioned: bool) -> Result<(), Error> {
(**self).set_commissioned(commissioned)
}
fn reset(&mut self) -> Result<(), Error> {
(**self).reset()
}
fn load(&mut self, data: &[u8]) -> Result<(), Error> {
(**self).load(data)
}
fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
(**self).save(buf)
}
}
pub trait NetworksAccess {
fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R;
}
impl<T> NetworksAccess for &T
where
T: NetworksAccess,
{
fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
(*self).access(f)
}
}
pub struct DummyNetworkAccess;
impl NetworksAccess for DummyNetworkAccess {
fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
f(&mut DummyNetworks)
}
}
pub struct DummyNetworks;
impl Networks for DummyNetworks {
fn max_networks(&self) -> Result<u8, Error> {
Ok(0)
}
fn networks(&self, _f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error> {
Ok(())
}
fn creds(
&self,
_network_id: &[u8],
_f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<u8, NetworksError> {
Err(NetworksError::NetworkIdNotFound)
}
fn next_creds(
&self,
_last_network_id: Option<&[u8]>,
_f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<bool, Error> {
Ok(false)
}
fn enabled(&self) -> Result<bool, Error> {
Ok(false)
}
fn set_enabled(&mut self, _enabled: bool) -> Result<(), Error> {
Ok(())
}
fn add_or_update(&mut self, _creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
}
fn reorder(&mut self, _index: u8, _network_id: &[u8]) -> Result<u8, NetworksError> {
Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
}
fn remove(&mut self, _network_id: &[u8]) -> Result<u8, NetworksError> {
Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
}
fn commissioned(&self) -> Result<bool, Error> {
Ok(false)
}
fn set_commissioned(&mut self, _commissioned: bool) -> Result<(), Error> {
Ok(())
}
fn reset(&mut self) -> Result<(), Error> {
Ok(())
}
fn load(&mut self, _data: &[u8]) -> Result<(), Error> {
Ok(())
}
fn save(&self, _buf: &mut [u8]) -> Result<Option<usize>, Error> {
Ok(None)
}
}
pub trait NetCtl {
fn net_type(&self) -> NetworkType;
fn connect_max_time_seconds(&self) -> u8 {
30
}
fn scan_max_time_seconds(&self) -> u8 {
30
}
fn supported_wifi_bands<F>(&self, mut f: F) -> Result<(), Error>
where
F: FnMut(WiFiBandEnum) -> Result<(), Error>,
{
f(WiFiBandEnum::V2G4)
}
fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
ThreadCapabilitiesBitmap::empty()
}
fn thread_version(&self) -> u16 {
4
}
async fn scan<F>(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError>
where
F: FnMut(&NetworkScanInfo) -> Result<(), Error>;
async fn connect(&self, creds: &WirelessCreds) -> Result<(), NetCtlError>;
}
impl<T> NetCtl for &T
where
T: NetCtl,
{
fn net_type(&self) -> NetworkType {
(*self).net_type()
}
fn connect_max_time_seconds(&self) -> u8 {
(*self).connect_max_time_seconds()
}
fn scan_max_time_seconds(&self) -> u8 {
(*self).scan_max_time_seconds()
}
fn supported_wifi_bands<F>(&self, f: F) -> Result<(), Error>
where
F: FnMut(WiFiBandEnum) -> Result<(), Error>,
{
(*self).supported_wifi_bands(f)
}
fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
(*self).supported_thread_features()
}
fn thread_version(&self) -> u16 {
(*self).thread_version()
}
fn scan<F>(&self, network: Option<&[u8]>, f: F) -> impl Future<Output = Result<(), NetCtlError>>
where
F: FnMut(&NetworkScanInfo) -> Result<(), Error>,
{
(*self).scan(network, f)
}
fn connect(&self, creds: &WirelessCreds<'_>) -> impl Future<Output = Result<(), NetCtlError>> {
(*self).connect(creds)
}
}
pub trait NetCtlStatus {
fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error>;
fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
where
F: FnOnce(Option<&[u8]>) -> Result<R, Error>;
fn last_connect_error_value(&self) -> Result<Option<i32>, Error>;
}
impl<T> NetCtlStatus for &T
where
T: NetCtlStatus,
{
fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error> {
(*self).last_networking_status()
}
fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
where
F: FnOnce(Option<&[u8]>) -> Result<R, Error>,
{
(*self).last_network_id(f)
}
fn last_connect_error_value(&self) -> Result<Option<i32>, Error> {
(*self).last_connect_error_value()
}
}
pub struct SharedNetworks<N> {
state: Mutex<RefCell<N>>,
state_changed: Notification,
}
impl<N> SharedNetworks<N> {
pub const fn new(networks: N) -> Self {
Self {
state: Mutex::new(RefCell::new(networks)),
state_changed: Notification::new(),
}
}
pub fn init(networks: impl Init<N>) -> impl Init<Self> {
init!(Self {
state <- Mutex::init(RefCell::init(networks)),
state_changed: Notification::new(),
})
}
pub fn get_mut(&mut self) -> &mut RefCell<N> {
self.state.get_mut()
}
pub fn wait_state_changed(&self) -> impl Future<Output = ()> + '_ {
self.state_changed.wait()
}
}
impl<N> DynBase for SharedNetworks<N> where N: Send {}
impl<N> NetworksAccess for SharedNetworks<N>
where
N: Networks,
{
fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
self.state.lock(|state| {
let mut networks = state.borrow_mut();
let mut instance = SharedNetworksInstance {
networks: &mut *networks,
changed: &self.state_changed,
};
f(&mut instance)
})
}
}
impl<N> NetChangeNotif for SharedNetworks<N> {
fn wait_changed(&self) -> impl Future<Output = ()> {
self.state_changed.wait()
}
}
pub struct SharedNetworksInstance<'a> {
networks: &'a mut dyn Networks,
changed: &'a Notification,
}
impl Networks for SharedNetworksInstance<'_> {
fn max_networks(&self) -> Result<u8, Error> {
self.networks.max_networks()
}
fn networks(&self, f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error> {
self.networks.networks(f)
}
fn creds(
&self,
network_id: &[u8],
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<u8, NetworksError> {
self.networks.creds(network_id, f)
}
fn next_creds(
&self,
last_network_id: Option<&[u8]>,
f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
) -> Result<bool, Error> {
self.networks.next_creds(last_network_id, f)
}
fn enabled(&self) -> Result<bool, Error> {
self.networks.enabled()
}
fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
self.networks.set_enabled(enabled)?;
self.changed.notify();
Ok(())
}
fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
let index = self.networks.add_or_update(creds)?;
self.changed.notify();
Ok(index)
}
fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
let index = self.networks.reorder(index, network_id)?;
self.changed.notify();
Ok(index)
}
fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
let index = self.networks.remove(network_id)?;
self.changed.notify();
Ok(index)
}
fn commissioned(&self) -> Result<bool, Error> {
self.networks.commissioned()
}
fn set_commissioned(&mut self, commissioned: bool) -> Result<(), Error> {
self.networks.set_commissioned(commissioned)?;
self.changed.notify();
Ok(())
}
fn reset(&mut self) -> Result<(), Error> {
self.networks.reset()?;
self.changed.notify();
Ok(())
}
fn load(&mut self, data: &[u8]) -> Result<(), Error> {
self.networks.load(data)
}
fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
let len = self.networks.save(buf)?;
Ok(len)
}
}
#[derive(Clone)]
pub struct NetCommHandler<T> {
dataver: Dataver,
net_ctl: T,
}
impl<T> NetCommHandler<T> {
pub const fn new(dataver: Dataver, net_ctl: T) -> Self {
Self { dataver, net_ctl }
}
pub const fn adapt(self) -> HandlerAsyncAdaptor<Self> {
HandlerAsyncAdaptor(self)
}
}
impl<T> ClusterAsyncHandler for NetCommHandler<T>
where
T: NetCtl + NetCtlStatus,
{
const CLUSTER: Cluster<'static> = NetworkType::Ethernet.cluster();
fn dataver(&self) -> u32 {
self.dataver.get()
}
fn dataver_changed(&self) {
self.dataver.changed();
}
fn max_networks(&self, ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
ready(ctx.networks().access(|networks| networks.max_networks()))
}
fn connect_max_time_seconds(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<u8, Error>> {
ready(Ok(self.net_ctl.connect_max_time_seconds()))
}
fn scan_max_time_seconds(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<u8, Error>> {
ready(Ok(self.net_ctl.scan_max_time_seconds()))
}
fn supported_wi_fi_bands<P: TLVBuilderParent>(
&self,
_ctx: impl ReadContext,
builder: ArrayAttributeRead<
ToTLVArrayBuilder<P, WiFiBandEnum>,
ToTLVBuilder<P, WiFiBandEnum>,
>,
) -> impl Future<Output = Result<P, Error>> {
ready(match builder {
ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| {
let mut builder = Some(builder);
self.net_ctl.supported_wifi_bands(|band| {
builder = Some(unwrap!(builder.take()).push(&band)?);
Ok(())
})?;
unwrap!(builder.take()).end()
}),
ArrayAttributeRead::ReadOne(index, builder) => {
let mut current = 0;
let mut builder = Some(builder);
let mut parent = None;
match self.net_ctl.supported_wifi_bands(|band| {
if current == index {
parent = Some(unwrap!(builder.take()).set(&band)?);
}
current += 1;
Ok(())
}) {
Err(e) => Err(e),
Ok(()) => {
if let Some(parent) = parent {
Ok(parent)
} else {
Err(ErrorCode::ConstraintError.into())
}
}
}
}
ArrayAttributeRead::ReadNone(builder) => builder.end(),
})
}
fn supported_thread_features(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<ThreadCapabilitiesBitmap, Error>> {
ready(Ok(self.net_ctl.supported_thread_features()))
}
fn thread_version(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u16, Error>> {
ready(Ok(self.net_ctl.thread_version()))
}
fn networks<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: ArrayAttributeRead<NetworkInfoStructArrayBuilder<P>, NetworkInfoStructBuilder<P>>,
) -> impl Future<Output = Result<P, Error>> {
ready(ctx.networks().access(|networks| match builder {
ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| {
let mut builder = Some(builder);
networks.networks(&mut |ni| {
builder = Some(ni.read_into(unwrap!(builder.take()).push()?)?);
Ok(())
})?;
unwrap!(builder.take()).end()
}),
ArrayAttributeRead::ReadOne(index, builder) => {
let mut current = 0;
let mut builder = Some(builder);
let mut parent = None;
networks.networks(&mut |ni| {
if current == index {
parent = Some(ni.read_into(unwrap!(builder.take()))?);
}
current += 1;
Ok(())
})?;
if let Some(parent) = parent {
Ok(parent)
} else {
Err(ErrorCode::ConstraintError.into())
}
}
ArrayAttributeRead::ReadNone(builder) => builder.end(),
}))
}
fn interface_enabled(
&self,
ctx: impl ReadContext,
) -> impl Future<Output = Result<bool, Error>> {
ready(ctx.networks().access(|networks| networks.enabled()))
}
fn last_networking_status(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<NetworkCommissioningStatusEnum>, Error>> {
ready(self.net_ctl.last_networking_status().map(Nullable::new))
}
fn last_network_id<P: TLVBuilderParent>(
&self,
_ctx: impl ReadContext,
builder: NullableBuilder<P, OctetsBuilder<P>>,
) -> impl Future<Output = Result<P, Error>> {
ready(self.net_ctl.last_network_id(|network_id| {
if let Some(network_id) = network_id {
builder.non_null()?.set(Octets::new(network_id))
} else {
builder.null()
}
}))
}
fn last_connect_error_value(
&self,
_ctx: impl ReadContext,
) -> impl Future<Output = Result<Nullable<i32>, Error>> {
ready(self.net_ctl.last_connect_error_value().map(Nullable::new))
}
async fn set_interface_enabled(
&self,
ctx: impl WriteContext,
value: bool,
) -> Result<(), Error> {
let mut persist = Persist::new(ctx.kv());
ctx.exchange().with_state(|state| {
ctx.networks().access(|networks| {
networks.set_enabled(value)?;
if !state.failsafe.is_armed() {
persist.store(NETWORKS_KEY, |buf| networks.save(buf))?;
}
Ok(())
})
})?;
persist.run()
}
async fn handle_scan_networks<P: TLVBuilderParent>(
&self,
_ctx: impl InvokeContext,
request: ScanNetworksRequest<'_>,
response: ScanNetworksResponseBuilder<P>,
) -> Result<P, Error> {
match self.net_ctl.net_type() {
NetworkType::Thread => {
let mut builder = Some(response);
let mut array_builder = None;
let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl(
self.net_ctl
.scan(
request
.ssid()?
.as_ref()
.and_then(|ssid| ssid.as_opt_ref())
.map(|ssid| ssid.0),
|network| {
let abuilder = if let Some(builder) = builder.take() {
builder
.networking_status(NetworkCommissioningStatusEnum::Success)?
.debug_text(None)?
.wi_fi_scan_results()?
.none()
.thread_scan_results()?
.some()?
} else {
unwrap!(array_builder.take())
};
array_builder = Some(network.thread_read_into(abuilder.push()?)?);
Ok(())
},
)
.await
.map(|_| 0),
)?;
if let Some(builder) = builder {
builder
.networking_status(status)?
.debug_text(None)?
.wi_fi_scan_results()?
.none()
.thread_scan_results()?
.none()
.end()
} else {
unwrap!(array_builder.take()).end()?.end()
}
}
NetworkType::Wifi => {
let mut builder = Some(response);
let mut array_builder = None;
let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl(
self.net_ctl
.scan(
request
.ssid()?
.as_ref()
.and_then(|ssid| ssid.as_opt_ref())
.map(|ssid| ssid.0),
|network| {
let abuilder = if let Some(builder) = builder.take() {
builder
.networking_status(NetworkCommissioningStatusEnum::Success)?
.debug_text(None)?
.wi_fi_scan_results()?
.some()?
} else {
unwrap!(array_builder.take())
};
array_builder = Some(network.wifi_read_into(abuilder.push()?)?);
Ok(())
},
)
.await
.map(|_| 0),
)?;
if let Some(builder) = builder {
builder
.networking_status(status)?
.debug_text(None)?
.wi_fi_scan_results()?
.none()
.thread_scan_results()?
.none()
.end()
} else {
unwrap!(array_builder.take())
.end()?
.thread_scan_results()?
.none()
.end()
}
}
NetworkType::Ethernet => Err(ErrorCode::InvalidAction.into()),
}
}
async fn handle_add_or_update_wi_fi_network<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: AddOrUpdateWiFiNetworkRequest<'_>,
response: NetworkConfigResponseBuilder<P>,
) -> Result<P, Error> {
let (status, _, index) = NetworkCommissioningStatusEnum::map(
GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
ctx.networks().access(|networks| {
let index = networks.add_or_update(&WirelessCreds::Wifi {
ssid: request.ssid()?.0,
pass: request.credentials()?.0,
})?;
Ok(index)
})
}),
)?;
ctx.notify_own_cluster_changed();
status.read_into(index, response)
}
async fn handle_add_or_update_thread_network<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: AddOrUpdateThreadNetworkRequest<'_>,
response: NetworkConfigResponseBuilder<P>,
) -> Result<P, Error> {
let (status, _, index) = NetworkCommissioningStatusEnum::map(
GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
ctx.networks().access(|networks| {
let index = networks.add_or_update(&WirelessCreds::Thread {
dataset_tlv: request.operational_dataset()?.0,
})?;
Ok(index)
})
}),
)?;
ctx.notify_own_cluster_changed();
status.read_into(index, response)
}
async fn handle_remove_network<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: RemoveNetworkRequest<'_>,
response: NetworkConfigResponseBuilder<P>,
) -> Result<P, Error> {
let (status, _, index) = NetworkCommissioningStatusEnum::map(
GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
ctx.networks().access(|networks| {
let index = networks.remove(request.network_id()?.0)?;
Ok(index)
})
}),
)?;
ctx.notify_own_cluster_changed();
status.read_into(index, response)
}
async fn handle_connect_network<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: ConnectNetworkRequest<'_>,
mut response: ConnectNetworkResponseBuilder<P>,
) -> Result<P, Error> {
if request.network_id()?.0.len() > MAX_WIRELESS_NETWORK_ID_LEN {
return Err(ErrorCode::ConstraintError.into());
}
let (status, err_code) = match self.net_ctl.net_type() {
NetworkType::Thread => {
let dataset_buf = response.writer().available_space();
let mut dataset_len = 0;
let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map(
GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
ctx.networks().access(|networks| {
networks.creds(request.network_id()?.0, &mut |creds| {
let WirelessCreds::Thread { dataset_tlv } = creds else {
error!("Thread creds expected");
return Err(ErrorCode::InvalidAction.into());
};
if dataset_tlv.len() > dataset_buf.len() {
error!("Dataset too large");
return Err(ErrorCode::ConstraintError.into());
}
dataset_buf[..dataset_tlv.len()].copy_from_slice(dataset_tlv);
dataset_len = dataset_tlv.len();
Ok(())
})
})
}),
)?;
if matches!(status, NetworkCommissioningStatusEnum::Success) {
(status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl(
self.net_ctl
.connect(&WirelessCreds::Thread {
dataset_tlv: &dataset_buf[..dataset_len],
})
.await,
)?;
}
(status, err_code)
}
NetworkType::Wifi => {
let buf = response.writer().available_space();
let (ssid_buf, pass_buf) = buf.split_at_mut(buf.len() / 2);
let mut ssid_len = 0;
let mut pass_len = 0;
let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map(
GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
ctx.networks().access(|networks| {
networks.creds(request.network_id()?.0, &mut |creds| {
let WirelessCreds::Wifi { ssid, pass } = creds else {
error!("Wifi creds expected");
return Err(ErrorCode::InvalidAction.into());
};
if ssid.len() > ssid_buf.len() {
error!("SSID too large");
return Err(ErrorCode::ConstraintError.into());
}
if pass.len() > pass_buf.len() {
error!("Password too large");
return Err(ErrorCode::ConstraintError.into());
}
ssid_buf[..ssid.len()].copy_from_slice(ssid);
ssid_len = ssid.len();
pass_buf[..pass.len()].copy_from_slice(pass);
pass_len = pass.len();
Ok(())
})
})
}),
)?;
if matches!(status, NetworkCommissioningStatusEnum::Success) {
(status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl(
self.net_ctl
.connect(&WirelessCreds::Wifi {
ssid: &ssid_buf[..ssid_len],
pass: &pass_buf[..pass_len],
})
.await,
)?;
}
(status, err_code)
}
NetworkType::Ethernet => {
return Err(ErrorCode::InvalidAction.into());
}
};
ctx.notify_own_cluster_changed();
response
.networking_status(status)?
.debug_text(None)?
.error_value(Nullable::new(err_code))?
.end()
}
async fn handle_reorder_network<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: ReorderNetworkRequest<'_>,
response: NetworkConfigResponseBuilder<P>,
) -> Result<P, Error> {
let (status, _, index) = NetworkCommissioningStatusEnum::map(
GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
ctx.networks().access(|networks| {
let index =
networks.reorder(request.network_index()? as _, request.network_id()?.0)?;
Ok(index)
})
}),
)?;
ctx.notify_own_cluster_changed();
status.read_into(index, response)
}
fn handle_query_identity<P: TLVBuilderParent>(
&self,
_ctx: impl InvokeContext,
_request: QueryIdentityRequest<'_>,
_response: QueryIdentityResponseBuilder<P>,
) -> impl Future<Output = Result<P, Error>> {
ready(Err(ErrorCode::CommandNotFound.into()))
}
}
impl<T> Debug for NetCommHandler<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NetCommHandler")
.field("dataver", &self.dataver.get())
.finish()
}
}
#[cfg(feature = "defmt")]
impl<T> defmt::Format for NetCommHandler<T> {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "NetCommHandler {{ dataver: {} }}", self.dataver.get());
}
}