use heapless::Vec;
pub const SOF: u8 = 0xA5;
pub const MAX_PAYLOAD: usize = 128;
pub const DEFAULT_GATEWAY_H2_BAUD: u32 = 115_200;
pub const DEFAULT_SPINEL_MAX_FRAME_SIZE: usize = 2048;
pub const GATEWAY_H2_OPENTHREAD_BAUD: u32 = DEFAULT_GATEWAY_H2_BAUD;
pub const GATEWAY_H2_MAX_SPINEL_FRAME_SIZE: usize = DEFAULT_SPINEL_MAX_FRAME_SIZE;
pub const GATEWAY_H2_MAX_ENCODED_SPINEL_FRAME_SIZE: usize =
(GATEWAY_H2_MAX_SPINEL_FRAME_SIZE + 2) * 2 + 2;
pub const GATEWAY_H2_MIN_RX_BUFFER_SIZE: usize = GATEWAY_H2_MAX_ENCODED_SPINEL_FRAME_SIZE * 2;
pub const GATEWAY_H2_MIN_TX_BUFFER_SIZE: usize = GATEWAY_H2_MAX_ENCODED_SPINEL_FRAME_SIZE;
pub const fn validate_openthread_buffer_capacities(
rx: usize,
tx: usize,
) -> Result<(), GatewayH2BufferCapacityError> {
if rx < GATEWAY_H2_MIN_RX_BUFFER_SIZE {
Err(GatewayH2BufferCapacityError::RxTooSmall)
} else if tx < GATEWAY_H2_MIN_TX_BUFFER_SIZE {
Err(GatewayH2BufferCapacityError::TxTooSmall)
} else {
Ok(())
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GatewayH2BufferCapacityError {
RxTooSmall,
TxTooSmall,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FrameKind {
Request = 0x01,
Response = 0x02,
Event = 0x03,
Error = 0x7F,
}
impl FrameKind {
const fn from_u8(value: u8) -> Option<Self> {
match value {
0x01 => Some(Self::Request),
0x02 => Some(Self::Response),
0x03 => Some(Self::Event),
0x7F => Some(Self::Error),
_ => None,
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransportError {
PayloadTooLong,
BufferTooSmall,
InvalidStart,
InvalidKind,
InvalidLength,
BadChecksum,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct H2Frame<const N: usize = MAX_PAYLOAD> {
pub kind: FrameKind,
pub id: u8,
pub payload: Vec<u8, N>,
}
impl<const N: usize> H2Frame<N> {
pub fn new(kind: FrameKind, id: u8, payload: &[u8]) -> Self {
let mut data = Vec::new();
for byte in payload.iter().copied().take(N) {
let _ = data.push(byte);
}
Self {
kind,
id,
payload: data,
}
}
pub fn encode<const OUT: usize>(&self) -> Result<Vec<u8, OUT>, TransportError> {
if self.payload.len() > u8::MAX as usize {
return Err(TransportError::PayloadTooLong);
}
let needed = 5 + self.payload.len();
if OUT < needed {
return Err(TransportError::BufferTooSmall);
}
let mut out = Vec::new();
out.push(SOF).map_err(|_| TransportError::BufferTooSmall)?;
out.push(self.kind as u8)
.map_err(|_| TransportError::BufferTooSmall)?;
out.push(self.id)
.map_err(|_| TransportError::BufferTooSmall)?;
out.push(self.payload.len() as u8)
.map_err(|_| TransportError::BufferTooSmall)?;
for byte in self.payload.iter().copied() {
out.push(byte).map_err(|_| TransportError::BufferTooSmall)?;
}
out.push(checksum(&out[1..]))
.map_err(|_| TransportError::BufferTooSmall)?;
Ok(out)
}
pub fn decode(bytes: &[u8]) -> Result<Self, TransportError> {
if bytes.len() < 5 {
return Err(TransportError::InvalidLength);
}
if bytes[0] != SOF {
return Err(TransportError::InvalidStart);
}
let kind = FrameKind::from_u8(bytes[1]).ok_or(TransportError::InvalidKind)?;
let id = bytes[2];
let len = usize::from(bytes[3]);
if bytes.len() != len + 5 {
return Err(TransportError::InvalidLength);
}
if checksum(&bytes[1..bytes.len() - 1]) != bytes[bytes.len() - 1] {
return Err(TransportError::BadChecksum);
}
if len > N {
return Err(TransportError::PayloadTooLong);
}
let mut payload = Vec::new();
for byte in bytes[4..4 + len].iter().copied() {
payload
.push(byte)
.map_err(|_| TransportError::PayloadTooLong)?;
}
Ok(Self { kind, id, payload })
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GatewayH2FirmwareMode {
OpenThreadRcp,
OpenThreadNcp,
CustomThreadCoprocessor,
CustomMatterBridge,
Unknown,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GatewayH2OpenThreadConfig {
pub baud: u32,
pub max_frame_size: usize,
pub firmware_mode: GatewayH2FirmwareMode,
pub hdlc_lite: bool,
pub has_crc: bool,
}
impl Default for GatewayH2OpenThreadConfig {
fn default() -> Self {
Self::OPENTHREAD_RCP
}
}
impl GatewayH2OpenThreadConfig {
pub const OPENTHREAD_RCP: Self = Self {
baud: DEFAULT_GATEWAY_H2_BAUD,
max_frame_size: DEFAULT_SPINEL_MAX_FRAME_SIZE,
firmware_mode: GatewayH2FirmwareMode::OpenThreadRcp,
hdlc_lite: true,
has_crc: true,
};
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ThreadRole {
Disabled,
Detached,
Child,
Router,
Leader,
BorderRouter,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GatewayH2Event {
Ready,
Attached(ThreadRole),
Detached,
DatasetAccepted,
DatasetRejected,
Error,
}
pub trait GatewayH2Transport {
type Error;
fn send<const N: usize>(&mut self, frame: &H2Frame<N>) -> Result<(), Self::Error>;
fn poll_receive<const N: usize>(&mut self) -> Result<Option<H2Frame<N>>, Self::Error>;
}
pub trait GatewayH2SpinelTransport {
type Error;
fn send_spinel_frame(&mut self, frame: &[u8]) -> Result<(), Self::Error>;
fn poll_spinel_frame(&mut self, out: &mut [u8]) -> Result<Option<usize>, Self::Error>;
}
pub trait AsyncGatewayH2SpinelTransport {
type Error;
fn send_spinel_frame(
&mut self,
frame: &[u8],
) -> impl core::future::Future<Output = Result<(), Self::Error>>;
fn receive_spinel_frame(
&mut self,
out: &mut [u8],
) -> impl core::future::Future<Output = Result<usize, Self::Error>>;
}
pub trait GatewayH2ThreadController {
type Error;
fn is_ready(&mut self) -> Result<bool, Self::Error>;
fn set_active_dataset(&mut self, dataset_tlv: &[u8]) -> Result<(), Self::Error>;
fn attach(&mut self) -> Result<(), Self::Error>;
fn detach(&mut self) -> Result<(), Self::Error>;
fn role(&mut self) -> Result<ThreadRole, Self::Error>;
fn poll_event(&mut self, out: &mut [u8]) -> Result<Option<GatewayH2Event>, Self::Error>;
}
pub const fn checksum(bytes: &[u8]) -> u8 {
let mut sum = 0u8;
let mut index = 0;
while index < bytes.len() {
sum = sum.wrapping_add(bytes[index]);
index += 1;
}
!sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encodes_and_decodes_frame() {
let frame = H2Frame::<16>::new(FrameKind::Request, 7, b"state");
let bytes = frame.encode::<16>().unwrap();
let decoded = H2Frame::<16>::decode(&bytes).unwrap();
assert_eq!(decoded, frame);
}
#[test]
fn validates_buffer_capacities() {
assert_eq!(
validate_openthread_buffer_capacities(
GATEWAY_H2_MIN_RX_BUFFER_SIZE - 1,
GATEWAY_H2_MIN_TX_BUFFER_SIZE
),
Err(GatewayH2BufferCapacityError::RxTooSmall)
);
assert_eq!(
validate_openthread_buffer_capacities(
GATEWAY_H2_MIN_RX_BUFFER_SIZE,
GATEWAY_H2_MIN_TX_BUFFER_SIZE - 1
),
Err(GatewayH2BufferCapacityError::TxTooSmall)
);
assert_eq!(
validate_openthread_buffer_capacities(
GATEWAY_H2_MIN_RX_BUFFER_SIZE,
GATEWAY_H2_MIN_TX_BUFFER_SIZE
),
Ok(())
);
assert_eq!(
GatewayH2OpenThreadConfig::default(),
GatewayH2OpenThreadConfig::OPENTHREAD_RCP
);
}
#[test]
fn rejects_bad_checksum() {
let frame = H2Frame::<16>::new(FrameKind::Event, 1, b"x");
let mut bytes = frame.encode::<16>().unwrap();
let last = bytes.len() - 1;
bytes[last] ^= 0x01;
assert_eq!(
H2Frame::<16>::decode(&bytes),
Err(TransportError::BadChecksum)
);
}
}