const MSG_RX_STATE_BITMAP_LEN: u32 = 16;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RxCtrState {
max_ctr: u32,
ctr_bitmap: u16,
}
impl RxCtrState {
pub const fn new(max_ctr: u32) -> Self {
Self {
max_ctr,
ctr_bitmap: 0xffff,
}
}
fn contains(&self, bit_number: u32) -> bool {
(self.ctr_bitmap & (1 << bit_number)) != 0
}
fn insert(&mut self, bit_number: u32) {
self.ctr_bitmap |= 1 << bit_number;
}
pub fn post_recv(&mut self, msg_ctr: u32, is_encrypted: bool, with_rollover: bool) -> bool {
if msg_ctr == self.max_ctr {
return false;
}
let (is_forward, udiff) = if with_rollover {
let fwd = msg_ctr.wrapping_sub(self.max_ctr);
if fwd <= i32::MAX as u32 {
(true, fwd)
} else {
(false, self.max_ctr.wrapping_sub(msg_ctr))
}
} else {
(msg_ctr > self.max_ctr, msg_ctr.abs_diff(self.max_ctr))
};
if !is_forward && udiff <= MSG_RX_STATE_BITMAP_LEN {
let index = udiff - 1;
if self.contains(index) {
false
} else {
self.insert(index);
true
}
}
else if is_forward {
self.max_ctr = msg_ctr;
if udiff < MSG_RX_STATE_BITMAP_LEN {
self.ctr_bitmap <<= udiff;
self.insert(udiff - 1);
} else {
self.ctr_bitmap = 0xffff;
}
true
} else if !is_encrypted {
self.max_ctr = msg_ctr;
self.ctr_bitmap = 0xffff;
true
} else {
false
}
}
}
pub const MAX_GROUP_CTR_ENTRIES: usize = 16;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct GroupCtrEntry {
fab_idx: u8,
src_nodeid: u64,
rx_ctr: RxCtrState,
last_used: u32,
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GroupCtrStore {
entries: heapless::Vec<GroupCtrEntry, MAX_GROUP_CTR_ENTRIES>,
clock: u32,
}
impl GroupCtrStore {
pub const fn new() -> Self {
Self {
entries: heapless::Vec::new(),
clock: 0,
}
}
pub fn post_recv(&mut self, fab_idx: u8, src_nodeid: u64, msg_ctr: u32) -> bool {
self.clock = self.clock.wrapping_add(1);
for entry in &mut self.entries {
if entry.fab_idx == fab_idx && entry.src_nodeid == src_nodeid {
entry.last_used = self.clock;
return entry.rx_ctr.post_recv(msg_ctr, true, true);
}
}
let new_entry = GroupCtrEntry {
fab_idx,
src_nodeid,
rx_ctr: RxCtrState::new(msg_ctr),
last_used: self.clock,
};
if self.entries.len() < MAX_GROUP_CTR_ENTRIES {
unwrap!(self.entries.push(new_entry));
} else {
let lru_idx = self
.entries
.iter()
.enumerate()
.min_by_key(|(_, e)| e.last_used)
.map(|(i, _)| i)
.unwrap(); self.entries[lru_idx] = new_entry;
}
true
}
}
#[cfg(test)]
mod tests {
use super::RxCtrState;
const ENCRYPTED: bool = true;
const NOT_ENCRYPTED: bool = false;
fn assert_ndup(b: bool) {
assert!(b);
}
fn assert_dup(b: bool) {
assert!(!b);
}
#[test]
fn new_msg_ctr() {
let mut s = RxCtrState::new(101);
assert_ndup(s.post_recv(103, ENCRYPTED, false));
assert_ndup(s.post_recv(104, ENCRYPTED, false));
assert_ndup(s.post_recv(106, ENCRYPTED, false));
assert_eq!(s.max_ctr, 106);
assert_eq!(s.ctr_bitmap, 0b1111_1111_1111_0110);
assert_ndup(s.post_recv(118, NOT_ENCRYPTED, false));
assert_eq!(s.ctr_bitmap, 0b0110_1000_0000_0000);
assert_ndup(s.post_recv(119, NOT_ENCRYPTED, false));
assert_ndup(s.post_recv(121, NOT_ENCRYPTED, false));
assert_eq!(s.ctr_bitmap, 0b0100_0000_0000_0110);
}
#[test]
fn dup_max_ctr() {
let mut s = RxCtrState::new(101);
assert_ndup(s.post_recv(103, ENCRYPTED, false));
assert_dup(s.post_recv(103, ENCRYPTED, false));
assert_dup(s.post_recv(103, NOT_ENCRYPTED, false));
assert_eq!(s.max_ctr, 103);
assert_eq!(s.ctr_bitmap, 0b1111_1111_1111_1110);
}
#[test]
fn dup_in_rx_bitmap() {
let mut ctr = 101;
let mut s = RxCtrState::new(101);
for _ in 1..8 {
ctr += 2;
assert_ndup(s.post_recv(ctr, ENCRYPTED, false));
}
assert_ndup(s.post_recv(116, ENCRYPTED, false));
assert_ndup(s.post_recv(117, ENCRYPTED, false));
assert_eq!(s.max_ctr, 117);
assert_eq!(s.ctr_bitmap, 0b1010_1010_1010_1011);
assert_dup(s.post_recv(101, ENCRYPTED, false));
assert_dup(s.post_recv(101, NOT_ENCRYPTED, false));
assert_dup(s.post_recv(116, ENCRYPTED, false));
assert_dup(s.post_recv(116, NOT_ENCRYPTED, false));
assert_ndup(s.post_recv(102, ENCRYPTED, false));
assert_dup(s.post_recv(102, ENCRYPTED, false));
assert_eq!(s.ctr_bitmap, 0b1110_1010_1010_1011);
}
#[test]
fn valid_corners_in_rx_bitmap() {
let mut ctr = 102;
let mut s = RxCtrState::new(101);
for _ in 1..9 {
ctr += 2;
assert_ndup(s.post_recv(ctr, ENCRYPTED, false));
}
assert_eq!(s.max_ctr, 118);
assert_eq!(s.ctr_bitmap, 0b0010_1010_1010_1010);
assert_ndup(s.post_recv(102, ENCRYPTED, false));
assert_eq!(s.ctr_bitmap, 0b1010_1010_1010_1010);
assert_ndup(s.post_recv(117, ENCRYPTED, false));
assert_eq!(s.ctr_bitmap, 0b1010_1010_1010_1011);
}
#[test]
fn no_panic_on_large_diff() {
let mut s = RxCtrState::new(1);
assert_ndup(s.post_recv(0x8000_0000, ENCRYPTED, false));
let mut s = RxCtrState::new(0x8000_0000);
assert_dup(s.post_recv(1, ENCRYPTED, false));
}
#[test]
fn encrypted_wraparound() {
let mut s = RxCtrState::new(65534);
assert_ndup(s.post_recv(65535, ENCRYPTED, false));
assert_ndup(s.post_recv(65536, ENCRYPTED, false));
assert_dup(s.post_recv(0, ENCRYPTED, false));
}
#[test]
fn unencrypted_wraparound() {
let mut s = RxCtrState::new(65534);
assert_ndup(s.post_recv(65536, NOT_ENCRYPTED, false));
assert_ndup(s.post_recv(0, NOT_ENCRYPTED, false));
}
#[test]
fn unencrypted_device_reboot() {
info!("Sub 65532 is {:?}", 1_u16.overflowing_sub(65532));
info!("Sub 65535 is {:?}", 1_u16.overflowing_sub(65535));
info!("Sub 11-13 is {:?}", 11_u32.wrapping_sub(13_u32) as i32);
info!("Sub regular is {:?}", 2000_u16.overflowing_sub(1998));
let mut s = RxCtrState::new(20010);
assert_ndup(s.post_recv(20011, NOT_ENCRYPTED, false));
assert_ndup(s.post_recv(0, NOT_ENCRYPTED, false));
}
mod group_ctr {
use super::super::GroupCtrStore;
#[test]
fn trust_first_accepts_new_sender() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, 100));
}
#[test]
fn rejects_duplicate_counter() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, 100));
assert!(!store.post_recv(1, 0x1111, 100));
}
#[test]
fn accepts_incrementing_counters() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, 100));
assert!(store.post_recv(1, 0x1111, 101));
assert!(store.post_recv(1, 0x1111, 102));
}
#[test]
fn separate_tracking_per_sender() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, 100));
assert!(store.post_recv(1, 0x2222, 100)); assert!(store.post_recv(2, 0x1111, 100)); }
#[test]
fn rollover_accepts_counter_past_u32_max() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, u32::MAX - 1));
assert!(store.post_recv(1, 0x1111, u32::MAX));
assert!(store.post_recv(1, 0x1111, 0)); assert!(store.post_recv(1, 0x1111, 5));
assert!(!store.post_recv(1, 0x1111, 5)); }
#[test]
fn rollover_rejects_behind_window_as_replay() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, u32::MAX - 10));
assert!(store.post_recv(1, 0x1111, 100)); assert!(!store.post_recv(1, 0x1111, u32::MAX - 10));
}
#[test]
fn rollover_bitmap_window_across_boundary() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, u32::MAX - 5));
assert!(store.post_recv(1, 0x1111, 2)); assert!(!store.post_recv(1, 0x1111, u32::MAX - 5));
assert!(store.post_recv(1, 0x1111, u32::MAX - 4));
}
#[test]
fn rollover_antipode_is_behind() {
let mut store = GroupCtrStore::new();
assert!(store.post_recv(1, 0x1111, 100));
assert!(!store.post_recv(1, 0x1111, 100u32.wrapping_add(0x8000_0000)));
}
#[test]
fn evicts_lru_when_full() {
let mut store = GroupCtrStore::new();
for i in 0..super::super::MAX_GROUP_CTR_ENTRIES {
assert!(store.post_recv(1, i as u64, 100));
}
assert!(store.post_recv(1, 0xFFFF, 200));
assert!(store.post_recv(1, 0, 100));
}
}
}