use core::str::FromStr;
use twine_tlv::TlvCollection;
use crate::{
dataset::{
ActiveTimestamp, DelayTimer, ExtendedPanId, MeshLocalPrefix, NetworkKey, NetworkName,
PendingTimestamp, Pskc, SecurityPolicy, Timestamp,
},
radio::{Channel, ChannelMask, PanId},
TwineCodecError,
};
mod iter;
pub use iter::OperationalDatasetIter;
const OPERATIONAL_DATASET_MAX_SIZE: usize = 256;
macro_rules! decode_type {
($name:ident, $decode_type:ty) => {
pub fn $name(&self) -> Option<$decode_type> {
self.collection.decode_type_unchecked::<$decode_type>()
}
};
}
#[derive(Debug)]
pub struct OperationalDataset {
collection: TlvCollection<OPERATIONAL_DATASET_MAX_SIZE>,
}
impl OperationalDataset {
#[cfg(any(test, feature = "std"))]
pub fn random() -> Result<Self, TwineCodecError> {
let mut collection = TlvCollection::default();
use crate::dataset::timestamp::Authoritative;
let active_timestamp = Timestamp::now(Authoritative(false));
let _ = collection.push(ActiveTimestamp::from(active_timestamp))?;
let channel = Channel::random();
let _ = collection.push(channel)?;
let channel_mask = ChannelMask::default();
let _ = collection.push(channel_mask)?;
let xpan = ExtendedPanId::random();
let _ = collection.push(xpan)?;
let mesh_local_prefix = MeshLocalPrefix::random_ula();
let _ = collection.push(mesh_local_prefix)?;
let network_key = NetworkKey::random();
let _ = collection.push(network_key)?;
let pan_id = PanId::random();
let network_name = alloc::format!("Twine-{:x}", pan_id.get());
let _ = collection.push(NetworkName::from_str(&network_name)?)?;
let _ = collection.push(pan_id)?;
let pskc = Pskc::random();
let _ = collection.push(pskc)?;
let security_policy = SecurityPolicy::default();
let _ = collection.push(security_policy)?;
Ok(Self { collection })
}
pub fn active_timestamp(&self) -> Option<Timestamp> {
self.collection
.decode_type_unchecked::<ActiveTimestamp>()
.map(Timestamp::from)
}
pub fn set_active_timestamp(&mut self, timestamp: Timestamp) -> Result<(), TwineCodecError> {
let active_timestamp = ActiveTimestamp::from(timestamp);
self.collection.replace_or_push(active_timestamp)?;
Ok(())
}
pub fn pending_timestamp(&self) -> Option<Timestamp> {
self.collection
.decode_type_unchecked::<PendingTimestamp>()
.map(Timestamp::from)
}
decode_type!(delay_timer, DelayTimer);
decode_type!(channel, Channel);
decode_type!(pan_id, PanId);
decode_type!(channel_mask, ChannelMask);
decode_type!(extended_pan_id, ExtendedPanId);
decode_type!(network_name, NetworkName);
decode_type!(pskc, Pskc);
decode_type!(network_key, NetworkKey);
decode_type!(mesh_local_prefix, MeshLocalPrefix);
decode_type!(security_policy, SecurityPolicy);
#[cfg(any(test, feature = "std"))]
pub fn pretty_fmt(&self) {
std::println!("Operational Dataset: {:?}", self);
self.iter().for_each(|item| std::println!("{item:?}"));
}
pub fn iter(&self) -> OperationalDatasetIter<'_> {
OperationalDatasetIter {
inner: (&self.collection).into_iter(),
}
}
#[cfg(any(test, feature = "alloc"))]
pub fn as_hex_string(&self) -> alloc::string::String {
let mut hex_string = alloc::string::String::new();
for tlv in &self.collection {
hex_string.push_str(&hex::encode(tlv));
}
hex_string
}
}
impl FromStr for OperationalDataset {
type Err = TwineCodecError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if (s.len() & 1) != 0 {
return Err(TwineCodecError::HexDecodeError);
}
let n = s.len() / 2;
let mut buffer = [0_u8; OPERATIONAL_DATASET_MAX_SIZE];
if n > buffer.len() {
return Err(TwineCodecError::HexDecodeError);
}
hex::decode_to_slice(s, &mut buffer[..n]).map_err(|_| TwineCodecError::HexDecodeError)?;
let collection = TlvCollection::new_from_static(buffer);
Ok(Self { collection })
}
}
impl core::fmt::Display for OperationalDataset {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.iter().try_fold((), |_, item| writeln!(f, "{item}"))
}
}
#[cfg(test)]
mod tests {
use crate::{dataset::timestamp::Authoritative, SecurityPolicyBuilder};
use super::*;
#[test]
fn success_from_str() {
let dataset_str = "0e080000000000010000000300000c4a0300001335060004001fffe002081bb896bef533a5850708fd48b2e8c34e7dc70510e9b948988752752873570d09ada4d0be030f4f70656e5468726561642d623364650102b3de0410f9f07ed37fbb6828fb3b26b63bdea3c30c0402a0f7f8";
let dataset = OperationalDataset::from_str(dataset_str).unwrap();
let active_timestamp = dataset.active_timestamp().unwrap();
let channel = dataset.channel().unwrap();
let xpan = dataset.extended_pan_id().unwrap();
let mesh_local_prefix: MeshLocalPrefix = dataset.mesh_local_prefix().unwrap();
let network_key = dataset.network_key().unwrap();
let network_name = dataset.network_name().unwrap();
let pan_id = dataset.pan_id().unwrap();
let pskc = dataset.pskc().unwrap();
let security_policy = dataset.security_policy().unwrap();
assert_eq!(
active_timestamp,
Timestamp::from((1, 1, Authoritative(false)))
);
assert_eq!(channel, Channel::new(0, 12));
assert_eq!(
xpan,
ExtendedPanId::from([0x1b, 0xb8, 0x96, 0xbe, 0xf5, 0x33, 0xa5, 0x85])
);
assert_eq!(
mesh_local_prefix,
MeshLocalPrefix::from([0xfd, 0x48, 0xb2, 0xe8, 0xc3, 0x4e, 0x7d, 0xc7])
);
assert_eq!(
network_key,
NetworkKey::from(u128::from_be_bytes([
0xe9, 0xb9, 0x48, 0x98, 0x87, 0x52, 0x75, 0x28, 0x73, 0x57, 0x0d, 0x09, 0xad, 0xa4,
0xd0, 0xbe
]))
);
assert_eq!(
network_name,
NetworkName::from_str("OpenThread-b3de").unwrap()
);
assert_eq!(pan_id, PanId::from(0xb3de));
assert_eq!(
pskc,
Pskc::from([
0xf9, 0xf0, 0x7e, 0xd3, 0x7f, 0xbb, 0x68, 0x28, 0xfb, 0x3b, 0x26, 0xb6, 0x3b, 0xde,
0xa3, 0xc3
])
);
assert_eq!(
security_policy,
SecurityPolicyBuilder::with_default_policy()
.build()
.unwrap()
);
}
}