use crate::mac::frame::{
header::{PanId, ShortAddress},
DecodeError,
};
use crate::utils::OptionalFrom;
use byte::{check_len, BytesExt, TryRead, TryWrite};
extended_enum!(
CommandId, u8,
AssociationRequest => 1,
AssociationResponse => 2,
DisassociationNotification => 3,
DataRequest => 4,
PanIdConflictNotification => 5,
OrphanNotification => 6,
BeaconRequest => 7,
CoordinatorRealignment => 8,
GuaranteedTimeSlotRequest => 9,
);
const CAP_FFD: u8 = 0x02;
const CAP_MAINS_POWER: u8 = 0x04;
const CAP_IDLE_RECEIVE: u8 = 0x08;
const CAP_FRAME_PROTECTION: u8 = 0x40;
const CAP_ALLOCATE_ADDRESS: u8 = 0x80;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CapabilityInformation {
pub full_function_device: bool,
pub mains_power: bool,
pub idle_receive: bool,
pub frame_protection: bool,
pub allocate_address: bool,
}
impl From<u8> for CapabilityInformation {
fn from(byte: u8) -> Self {
let full_function_device = byte & CAP_FFD == CAP_FFD;
let mains_power = byte & CAP_MAINS_POWER == CAP_MAINS_POWER;
let idle_receive = byte & CAP_IDLE_RECEIVE == CAP_IDLE_RECEIVE;
let frame_protection =
byte & CAP_FRAME_PROTECTION == CAP_FRAME_PROTECTION;
let allocate_address =
byte & CAP_ALLOCATE_ADDRESS == CAP_ALLOCATE_ADDRESS;
Self {
full_function_device,
mains_power,
idle_receive,
frame_protection,
allocate_address,
}
}
}
impl From<CapabilityInformation> for u8 {
fn from(ar: CapabilityInformation) -> Self {
let mut byte = 0u8;
if ar.full_function_device {
byte = byte | CAP_FFD;
}
if ar.mains_power {
byte = byte | CAP_MAINS_POWER;
}
if ar.idle_receive {
byte = byte | CAP_IDLE_RECEIVE;
}
if ar.frame_protection {
byte = byte | CAP_FRAME_PROTECTION;
}
if ar.allocate_address {
byte = byte | CAP_ALLOCATE_ADDRESS;
}
byte
}
}
extended_enum!(
AssociationStatus, u8,
Successful => 0x00,
NetworkAtCapacity => 0x01,
AccessDenied => 0x02,
HoppingSequenceOffsetDuplication => 0x03,
FastAssociationSuccesful => 0x80,
);
extended_enum!(
DisassociationReason, u8,
CoordinatorLeave => 1,
DeviceLeave => 2,
);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CoordinatorRealignmentData {
pub pan_id: PanId,
pub coordinator_address: ShortAddress,
pub channel: u8,
pub device_address: ShortAddress,
pub channel_page: Option<u8>,
}
impl TryWrite for CoordinatorRealignmentData {
fn try_write(self, bytes: &mut [u8], _ctx: ()) -> byte::Result<usize> {
let offset = &mut 0;
bytes.write(offset, self.pan_id)?;
bytes.write(offset, self.coordinator_address)?;
bytes.write(offset, self.channel)?;
bytes.write(offset, self.device_address)?;
if let Some(channel_page) = self.channel_page {
bytes.write(offset, channel_page)?;
}
Ok(*offset)
}
}
impl TryRead<'_> for CoordinatorRealignmentData {
fn try_read(bytes: &[u8], _ctx: ()) -> byte::Result<(Self, usize)> {
let offset = &mut 0;
check_len(&bytes, 7)?;
let pan_id = bytes.read(offset)?;
let coordinator_address = bytes.read(offset)?;
let channel = bytes.read(offset)?;
let device_address = bytes.read(offset)?;
let channel_page = if bytes.len() > *offset {
Some(bytes.read(offset)?)
} else {
None
};
Ok((
Self {
pan_id,
coordinator_address,
channel,
device_address,
channel_page,
},
*offset,
))
}
}
const GTSC_RECEIVE_ONLY: u8 = 0x10;
const GTSC_ALLOCATION: u8 = 0x20;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GuaranteedTimeSlotCharacteristics {
pub count: u8,
pub receive_only: bool,
pub allocation: bool,
}
impl From<u8> for GuaranteedTimeSlotCharacteristics {
fn from(byte: u8) -> Self {
let receive_only = byte & GTSC_RECEIVE_ONLY == GTSC_RECEIVE_ONLY;
let allocation = byte & GTSC_ALLOCATION == GTSC_ALLOCATION;
Self {
count: (byte & 0x0f),
receive_only,
allocation,
}
}
}
impl From<GuaranteedTimeSlotCharacteristics> for u8 {
fn from(gtsc: GuaranteedTimeSlotCharacteristics) -> Self {
let mut byte = gtsc.count & 0x0f;
if gtsc.receive_only {
byte = byte | GTSC_RECEIVE_ONLY;
}
if gtsc.allocation {
byte = byte | GTSC_ALLOCATION;
}
byte
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Command {
AssociationRequest(CapabilityInformation),
AssociationResponse(ShortAddress, AssociationStatus),
DisassociationNotification(DisassociationReason),
DataRequest,
PanIdConflictNotification,
OrphanNotification,
BeaconRequest,
CoordinatorRealignment(CoordinatorRealignmentData),
GuaranteedTimeSlotRequest(GuaranteedTimeSlotCharacteristics),
}
impl TryWrite for Command {
fn try_write(self, bytes: &mut [u8], _ctx: ()) -> byte::Result<usize> {
let offset = &mut 0;
match self {
Command::AssociationRequest(capability) => {
bytes.write(offset, u8::from(CommandId::AssociationRequest))?;
bytes.write(offset, u8::from(capability))?;
}
Command::AssociationResponse(address, status) => {
bytes
.write(offset, u8::from(CommandId::AssociationResponse))?;
bytes.write(offset, address)?;
bytes.write(offset, u8::from(status))?;
}
Command::DisassociationNotification(reason) => {
bytes.write(
offset,
u8::from(CommandId::DisassociationNotification),
)?;
bytes.write(offset, u8::from(reason))?;
}
Command::DataRequest => {
bytes.write(offset, u8::from(CommandId::DataRequest))?;
}
Command::PanIdConflictNotification => {
bytes.write(
offset,
u8::from(CommandId::PanIdConflictNotification),
)?;
}
Command::OrphanNotification => {
bytes.write(offset, u8::from(CommandId::OrphanNotification))?;
}
Command::BeaconRequest => {
bytes.write(offset, u8::from(CommandId::BeaconRequest))?;
}
Command::CoordinatorRealignment(data) => {
bytes.write(
offset,
u8::from(CommandId::CoordinatorRealignment),
)?;
bytes.write(offset, data)?;
}
Command::GuaranteedTimeSlotRequest(characteristics) => {
bytes.write(
offset,
u8::from(CommandId::GuaranteedTimeSlotRequest),
)?;
bytes.write(offset, u8::from(characteristics))?;
}
}
Ok(*offset)
}
}
impl TryRead<'_> for Command {
fn try_read(bytes: &[u8], _ctx: ()) -> byte::Result<(Self, usize)> {
let offset = &mut 0;
let cmd = CommandId::optional_from(bytes.read::<u8>(offset)?)
.ok_or(DecodeError::InvalidValue)?;
Ok((
match cmd {
CommandId::AssociationRequest => {
let capability =
CapabilityInformation::from(bytes.read::<u8>(offset)?);
Command::AssociationRequest(capability)
}
CommandId::AssociationResponse => {
let address: ShortAddress = bytes.read(offset)?;
let status =
AssociationStatus::optional_from(bytes.read(offset)?)
.ok_or(DecodeError::InvalidValue)?;
Command::AssociationResponse(address, status)
}
CommandId::DisassociationNotification => {
let reason = DisassociationReason::optional_from(
bytes.read(offset)?,
)
.ok_or(DecodeError::InvalidValue)?;
Command::DisassociationNotification(reason)
}
CommandId::DataRequest => Command::DataRequest,
CommandId::PanIdConflictNotification => {
Command::PanIdConflictNotification
}
CommandId::OrphanNotification => Command::OrphanNotification,
CommandId::BeaconRequest => Command::BeaconRequest,
CommandId::CoordinatorRealignment => {
Command::CoordinatorRealignment(bytes.read(offset)?)
}
CommandId::GuaranteedTimeSlotRequest => {
let characteristics =
GuaranteedTimeSlotCharacteristics::from(
bytes.read::<u8>(offset)?,
);
Command::GuaranteedTimeSlotRequest(characteristics)
}
},
*offset,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_association_request() {
let data = [0x01, 0x8e];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::AssociationRequest(CapabilityInformation {
full_function_device: true,
mains_power: true,
idle_receive: true,
frame_protection: false,
allocate_address: true,
})
);
}
#[test]
fn encode_association_request() {
let command = Command::AssociationRequest(CapabilityInformation {
full_function_device: false,
mains_power: false,
idle_receive: false,
frame_protection: false,
allocate_address: false,
});
let mut data = [0u8; 32];
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x01, 0x00]);
let command = Command::AssociationRequest(CapabilityInformation {
full_function_device: true,
mains_power: false,
idle_receive: false,
frame_protection: false,
allocate_address: false,
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x01, 0x02]);
let command = Command::AssociationRequest(CapabilityInformation {
full_function_device: false,
mains_power: true,
idle_receive: false,
frame_protection: false,
allocate_address: false,
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x01, 0x04]);
let command = Command::AssociationRequest(CapabilityInformation {
full_function_device: false,
mains_power: false,
idle_receive: true,
frame_protection: false,
allocate_address: false,
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x01, 0x08]);
let command = Command::AssociationRequest(CapabilityInformation {
full_function_device: false,
mains_power: false,
idle_receive: false,
frame_protection: true,
allocate_address: false,
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x01, 0x40]);
let command = Command::AssociationRequest(CapabilityInformation {
full_function_device: false,
mains_power: false,
idle_receive: false,
frame_protection: false,
allocate_address: true,
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x01, 0x80]);
}
#[test]
fn decode_association_response() {
let data = [0x02, 0x40, 0x77, 0x00];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::AssociationResponse(
ShortAddress(0x7740),
AssociationStatus::Successful
)
);
let data = [0x02, 0xaa, 0x55, 0x01];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::AssociationResponse(
ShortAddress(0x55aa),
AssociationStatus::NetworkAtCapacity
)
);
let data = [0x02, 0x00, 0x00, 0x02];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::AssociationResponse(
ShortAddress(0x0000),
AssociationStatus::AccessDenied
)
);
let data = [0x02, 0x00, 0x00, 0x03];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::AssociationResponse(
ShortAddress(0x0000),
AssociationStatus::HoppingSequenceOffsetDuplication
)
);
let data = [0x02, 0x00, 0x00, 0x80];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::AssociationResponse(
ShortAddress(0x0000),
AssociationStatus::FastAssociationSuccesful
)
);
let data = [0x02, 0x00, 0x00, 0x04];
let result = data.read::<Command>(&mut len);
assert!(result.is_err());
let data = [0x02, 0x00, 0x00, 0x7f];
let result = data.read::<Command>(&mut len);
assert!(result.is_err());
let data = [0x02, 0x00, 0x00, 0x81];
let result = data.read::<Command>(&mut len);
assert!(result.is_err());
}
#[test]
fn encode_association_response() {
let mut data = [0u8; 4];
let command = Command::AssociationResponse(
ShortAddress(0x55aa),
AssociationStatus::Successful,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, data.len());
assert_eq!(data[..len], [0x02, 0xaa, 0x55, 0x00]);
let command = Command::AssociationResponse(
ShortAddress(0x1234),
AssociationStatus::NetworkAtCapacity,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, data.len());
assert_eq!(data[..len], [0x02, 0x34, 0x12, 0x01]);
let command = Command::AssociationResponse(
ShortAddress(0xcffe),
AssociationStatus::AccessDenied,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, data.len());
assert_eq!(data[..len], [0x02, 0xfe, 0xcf, 0x02]);
let command = Command::AssociationResponse(
ShortAddress(0xfedc),
AssociationStatus::HoppingSequenceOffsetDuplication,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, data.len());
assert_eq!(data[..len], [0x02, 0xdc, 0xfe, 0x03]);
let command = Command::AssociationResponse(
ShortAddress(0x0ff0),
AssociationStatus::FastAssociationSuccesful,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, data.len());
assert_eq!(data[..len], [0x02, 0xf0, 0x0f, 0x80]);
}
#[test]
fn decode_disassociation_notification() {
let data = [0x03, 0x01];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::DisassociationNotification(
DisassociationReason::CoordinatorLeave
)
);
let data = [0x03, 0x02];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::DisassociationNotification(
DisassociationReason::DeviceLeave
)
);
let data = [0x03, 0x00];
let result = data.read::<Command>(&mut len);
assert!(result.is_err());
let data = [0x03, 0x03];
let result = data.read::<Command>(&mut len);
assert!(result.is_err());
}
#[test]
fn encode_disassociation_notification() {
let mut data = [0u8; 32];
let command = Command::DisassociationNotification(
DisassociationReason::CoordinatorLeave,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x03, 0x01]);
let command = Command::DisassociationNotification(
DisassociationReason::DeviceLeave,
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x03, 0x02]);
}
#[test]
fn decode_coordinator_realignment() {
let data = [0x08, 0x23, 0x11, 0x01, 0x00, 0x0f, 0x34, 0x12];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::CoordinatorRealignment(CoordinatorRealignmentData {
pan_id: PanId(0x1123),
coordinator_address: ShortAddress(0x0001),
channel: 15,
device_address: ShortAddress(0x1234),
channel_page: None,
})
);
let data = [0x08, 0x34, 0x12, 0x21, 0x43, 0x0b, 0xcd, 0xab, 0x01];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::CoordinatorRealignment(CoordinatorRealignmentData {
pan_id: PanId(0x1234),
coordinator_address: ShortAddress(0x4321),
channel: 11,
device_address: ShortAddress(0xabcd),
channel_page: Some(1),
})
);
}
#[test]
fn encode_coordinator_realignment() {
let mut data = [0u8; 32];
let command =
Command::CoordinatorRealignment(CoordinatorRealignmentData {
pan_id: PanId(0x1123),
coordinator_address: ShortAddress(0x0001),
channel: 15,
device_address: ShortAddress(0x1234),
channel_page: None,
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 8);
assert_eq!(
&data[..len],
[0x08, 0x23, 0x11, 0x01, 0x00, 0x0f, 0x34, 0x12]
);
let command =
Command::CoordinatorRealignment(CoordinatorRealignmentData {
pan_id: PanId(0xbeef),
coordinator_address: ShortAddress(0xfeed),
channel: 26,
device_address: ShortAddress(0x1234),
channel_page: Some(15),
});
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 9);
assert_eq!(
&data[..len],
[0x08, 0xef, 0xbe, 0xed, 0xfe, 0x1a, 0x34, 0x12, 0x0f]
);
}
#[test]
fn decode_guaranteed_time_slot_request() {
let data = [0x09, 0x01];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::GuaranteedTimeSlotRequest(
GuaranteedTimeSlotCharacteristics {
count: 1,
receive_only: false,
allocation: false,
}
)
);
let data = [0x09, 0x12];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::GuaranteedTimeSlotRequest(
GuaranteedTimeSlotCharacteristics {
count: 2,
receive_only: true,
allocation: false,
}
)
);
let data = [0x09, 0x23];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(
command,
Command::GuaranteedTimeSlotRequest(
GuaranteedTimeSlotCharacteristics {
count: 3,
receive_only: false,
allocation: true,
}
)
);
}
#[test]
fn encode_guaranteed_time_slot_request() {
let mut data = [0u8; 32];
let command = Command::GuaranteedTimeSlotRequest(
GuaranteedTimeSlotCharacteristics {
count: 1,
receive_only: false,
allocation: false,
},
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x09, 0x01]);
let command = Command::GuaranteedTimeSlotRequest(
GuaranteedTimeSlotCharacteristics {
count: 15,
receive_only: true,
allocation: false,
},
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x09, 0x1f]);
let command = Command::GuaranteedTimeSlotRequest(
GuaranteedTimeSlotCharacteristics {
count: 15,
receive_only: false,
allocation: true,
},
);
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 2);
assert_eq!(data[..len], [0x09, 0x2f]);
}
#[test]
fn decode_other_commands() {
let data = [0x04];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(command, Command::DataRequest);
let data = [0x05];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(command, Command::PanIdConflictNotification);
let data = [0x06];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(command, Command::OrphanNotification);
let data = [0x07];
let mut len = 0usize;
let command: Command = data.read(&mut len).unwrap();
assert_eq!(len, data.len());
assert_eq!(command, Command::BeaconRequest);
}
#[test]
fn encode_other_commands() {
let mut data = [0u8; 32];
let command = Command::DataRequest;
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 1);
assert_eq!(data[..len], [0x04]);
let command = Command::PanIdConflictNotification;
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 1);
assert_eq!(data[..len], [0x05]);
let command = Command::OrphanNotification;
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 1);
assert_eq!(data[..len], [0x06]);
let command = Command::BeaconRequest;
let mut len = 0usize;
data.write(&mut len, command).unwrap();
assert_eq!(len, 1);
assert_eq!(data[..len], [0x07]);
}
}