| // 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::enums::*; |
| use anyhow::{format_err, Context as _}; |
| use core::convert::{TryFrom, TryInto}; |
| use spinel_pack::*; |
| use std::collections::HashSet; |
| use std::io; |
| use std::num::NonZeroU8; |
| |
| /// A type for decoding/encoding the Spinel header byte. |
| #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] |
| pub struct Header(u8); |
| |
| impl Header { |
| /// Creates a new instance of `Header` based on the given NLI and TID. |
| /// |
| /// Valid values for NLI are between 0 and 3, inclusive. |
| /// Valid values for TID are `None` or between `Some(1)` and `Some(15)`, inclusive. |
| /// |
| /// If either the NLI and/or TID are invalid, then |
| /// this constructor will return `None`. |
| pub fn new(nli: u8, tid: Option<NonZeroU8>) -> Option<Header> { |
| let tid = tid.map_or(0, NonZeroU8::get); |
| |
| if nli < 4 && tid < 16 { |
| Some(Header(0x80 + (nli << 4) + tid)) |
| } else { |
| None |
| } |
| } |
| |
| /// Network link ID. |
| pub fn nli(&self) -> u8 { |
| (self.0 & 0x30) >> 4 |
| } |
| |
| /// Transaction Id |
| pub fn tid(&self) -> Option<NonZeroU8> { |
| NonZeroU8::new(self.0 & 0x0F) |
| } |
| } |
| |
| impl std::fmt::Debug for Header { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| f.debug_struct("Header").field("nli", &self.nli()).field("tid", &self.tid()).finish() |
| } |
| } |
| |
| impl From<Header> for u8 { |
| fn from(header: Header) -> Self { |
| header.0 |
| } |
| } |
| |
| impl TryFrom<u8> for Header { |
| type Error = anyhow::Error; |
| fn try_from(x: u8) -> Result<Self, Self::Error> { |
| if x & 0xC0 != 0x80 { |
| Err(format_err!("Bad header byte"))?; |
| } |
| Ok(Header(x)) |
| } |
| } |
| |
| impl TryPackAs<u8> for Header { |
| fn pack_as_len(&self) -> std::io::Result<usize> { |
| Ok(1) |
| } |
| |
| fn try_pack_as<T: std::io::Write + ?Sized>(&self, buffer: &mut T) -> std::io::Result<usize> { |
| TryPackAs::<u8>::try_pack_as(&self.0, buffer) |
| } |
| } |
| |
| impl TryPack for Header { |
| fn pack_len(&self) -> io::Result<usize> { |
| Ok(1) |
| } |
| |
| fn try_pack<T: std::io::Write + ?Sized>(&self, buffer: &mut T) -> io::Result<usize> { |
| TryPackAs::<u8>::try_pack_as(&self.0, buffer) |
| } |
| } |
| |
| impl<'a> TryUnpackAs<'a, u8> for Header { |
| fn try_unpack_as(iter: &mut std::slice::Iter<'a, u8>) -> anyhow::Result<Self> { |
| let id: u8 = TryUnpackAs::<u8>::try_unpack_as(iter)?; |
| |
| id.try_into().context(UnpackingError::InvalidValue) |
| } |
| } |
| |
| impl<'a> TryUnpack<'a> for Header { |
| type Unpacked = Header; |
| fn try_unpack(iter: &mut std::slice::Iter<'a, u8>) -> anyhow::Result<Self::Unpacked> { |
| TryUnpackAs::<u8>::try_unpack_as(iter) |
| } |
| } |
| |
| /// Struct that represents a decoded Spinel command frame with a |
| /// borrowed reference to the payload. |
| #[spinel_packed("CiD")] |
| #[derive(Debug, Clone, Copy)] |
| pub struct SpinelFrameRef<'a> { |
| pub header: Header, |
| pub cmd: Cmd, |
| pub payload: &'a [u8], |
| } |
| |
| /// Struct that represents a decoded Spinel property payload with a |
| /// borrowed reference to the value. |
| #[spinel_packed("iD")] |
| #[derive(Debug, Clone, Copy)] |
| pub struct SpinelPropValueRef<'a> { |
| pub prop: Prop, |
| pub value: &'a [u8], |
| } |
| |
| /// Spinel Protocol Version struct, for determining device compatibility. |
| #[spinel_packed("ii")] |
| #[derive(Debug, Clone, Copy)] |
| pub struct ProtocolVersion(pub u32, pub u32); |
| |
| /// The MAC section of a spinel network scan result |
| #[spinel_packed("ESSC")] |
| #[derive(Debug)] |
| pub struct NetScanResultMac { |
| pub long_addr: EUI64, |
| pub short_addr: u16, |
| pub panid: u16, |
| pub lqi: u8, |
| } |
| |
| /// The NET section of a spinel network scan result |
| #[spinel_packed("iCUdd")] |
| #[derive(Debug)] |
| pub struct NetScanResultNet { |
| pub net_type: u32, |
| pub flags: u8, |
| pub network_name: Vec<u8>, |
| pub xpanid: Vec<u8>, |
| pub steering_data: Vec<u8>, |
| } |
| |
| /// A spinel network scan result |
| #[spinel_packed("Ccdd")] |
| #[derive(Debug)] |
| pub struct NetScanResult { |
| pub channel: u8, |
| pub rssi: i8, |
| pub mac: NetScanResultMac, |
| pub net: NetScanResultNet, |
| } |
| |
| /// A spinel energy scan result |
| #[spinel_packed("Cc")] |
| #[derive(Debug)] |
| pub struct EnergyScanResult { |
| pub channel: u8, |
| pub rssi: i8, |
| } |
| |
| /// A spinel IPv6 subnet |
| #[spinel_packed("6C")] |
| #[derive(Hash, Clone, Eq, PartialEq)] |
| pub struct Subnet { |
| pub addr: std::net::Ipv6Addr, |
| pub prefix_len: u8, |
| } |
| |
| impl std::fmt::Debug for Subnet { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}/{}", self.addr, self.prefix_len) |
| } |
| } |
| |
| impl std::fmt::Display for Subnet { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}/{}", self.addr, self.prefix_len) |
| } |
| } |
| |
| impl TryFrom<fidl_fuchsia_net::Subnet> for Subnet { |
| type Error = anyhow::Error; |
| fn try_from(x: fidl_fuchsia_net::Subnet) -> Result<Self, Self::Error> { |
| match x { |
| fidl_fuchsia_net::Subnet { |
| addr: fidl_fuchsia_net::IpAddress::Ipv6(addr), |
| prefix_len, |
| } => Ok(Subnet { addr: addr.addr.into(), prefix_len }), |
| _ => Err(format_err!("Cannot convert IPv4 subnet into IPv6 subnet")), |
| } |
| } |
| } |
| |
| /// A spinel address table entry from SPINEL_PROP_IPV6_ADDRESS_TABLE |
| #[spinel_packed("DLL")] |
| #[derive(Clone, Eq)] |
| pub struct AddressTableEntry { |
| pub subnet: Subnet, |
| pub preferred_lifetime: u32, |
| pub valid_lifetime: u32, |
| } |
| |
| impl std::fmt::Debug for AddressTableEntry { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}", self.subnet)?; |
| match (self.preferred_lifetime != 0, self.valid_lifetime != 0) { |
| (true, true) => Ok(()), |
| (false, true) => write!(f, " DEPRECATED"), |
| (_, false) => write!(f, " INVALID"), |
| } |
| } |
| } |
| |
| impl std::fmt::Display for AddressTableEntry { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}", self.subnet)?; |
| match (self.preferred_lifetime != 0, self.valid_lifetime != 0) { |
| (true, true) => Ok(()), |
| (false, true) => write!(f, " DEPRECATED"), |
| (_, false) => write!(f, " INVALID"), |
| } |
| } |
| } |
| |
| impl Default for AddressTableEntry { |
| fn default() -> Self { |
| AddressTableEntry { |
| subnet: Subnet { addr: std::net::Ipv6Addr::UNSPECIFIED, prefix_len: 0 }, |
| preferred_lifetime: std::u32::MAX, |
| valid_lifetime: std::u32::MAX, |
| } |
| } |
| } |
| |
| impl PartialEq for AddressTableEntry { |
| fn eq(&self, other: &Self) -> bool { |
| self.subnet == other.subnet |
| } |
| } |
| |
| impl std::hash::Hash for AddressTableEntry { |
| fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| self.subnet.hash(state); |
| } |
| } |
| |
| /// A spinel network packet |
| #[spinel_packed("dD")] |
| #[derive(Debug, Hash, Clone, Eq, PartialEq)] |
| pub struct NetworkPacket<'a> { |
| pub packet: &'a [u8], |
| pub metadata: &'a [u8], |
| } |
| |
| pub type AddressTable = HashSet<AddressTableEntry>; |
| |
| /// An allow list entry |
| #[spinel_packed("Ec")] |
| #[derive(Debug, Hash, Clone, Eq, PartialEq)] |
| pub struct AllowListEntry { |
| pub mac_addr: EUI64, |
| pub rssi: i8, |
| } |
| |
| pub type AllowList = HashSet<AllowListEntry>; |
| |
| /// An deny list entry |
| #[spinel_packed("E")] |
| #[derive(Debug, Hash, Clone, Eq, PartialEq)] |
| pub struct DenyListEntry { |
| pub mac_addr: EUI64, |
| } |
| |
| pub type DenyList = HashSet<DenyListEntry>; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use matches::assert_matches; |
| |
| #[test] |
| fn test_header() { |
| assert_eq!(Header::new(0, None), Some(Header(0x80))); |
| assert_eq!(Header::new(3, None), Some(Header(0xb0))); |
| assert_eq!(Header::new(4, None), None); |
| assert_eq!(Header::new(0, NonZeroU8::new(1)), Some(Header(0x81))); |
| assert_eq!(Header::new(0, NonZeroU8::new(15)), Some(Header(0x8F))); |
| assert_eq!(Header::new(0, NonZeroU8::new(16)), None); |
| |
| assert_eq!(Header::new(3, NonZeroU8::new(15)), Some(Header(0xBF))); |
| assert_eq!(Header::new(4, NonZeroU8::new(16)), None); |
| |
| assert_matches!(Header::try_from(0x00), Err(_)); |
| assert_matches!(Header::try_from(0xBF), Ok(Header(0xBF))); |
| assert_matches!(Header::try_from(0xFF), Err(_)); |
| |
| assert_eq!(Header(0x80).tid(), None); |
| assert_eq!(Header(0x80).nli(), 0); |
| assert_eq!(Header(0xBF).tid(), NonZeroU8::new(15)); |
| assert_eq!(Header(0xBF).nli(), 3); |
| } |
| } |