#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeMap, string::String, string::ToString, vec, vec::Vec};
#[cfg(feature = "std")]
use std::collections::BTreeMap;
use crate::NodeId;
pub const MIN_VALID_TIMESTAMP: u64 = 1577836800000;
pub const MAX_BATTERY_PERCENT: u8 = 100;
pub const MIN_HEART_RATE: u8 = 20;
pub const MAX_HEART_RATE: u8 = 250;
pub const MAX_EMERGENCY_ACKS: usize = 256;
pub const MAX_COUNTER_ENTRIES: usize = 256;
pub type Timestamp = u64;
#[derive(Debug, Clone, PartialEq)]
pub struct LwwRegister<T: Clone> {
value: T,
timestamp: Timestamp,
node_id: NodeId,
}
impl<T: Clone + Default> Default for LwwRegister<T> {
fn default() -> Self {
Self {
value: T::default(),
timestamp: 0,
node_id: NodeId::default(),
}
}
}
impl<T: Clone> LwwRegister<T> {
pub fn new(value: T, timestamp: Timestamp, node_id: NodeId) -> Self {
Self {
value,
timestamp,
node_id,
}
}
pub fn get(&self) -> &T {
&self.value
}
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn set(&mut self, value: T, timestamp: Timestamp, node_id: NodeId) -> bool {
if self.should_update(timestamp, &node_id) {
self.value = value;
self.timestamp = timestamp;
self.node_id = node_id;
true
} else {
false
}
}
pub fn merge(&mut self, other: &LwwRegister<T>) -> bool {
if self.should_update(other.timestamp, &other.node_id) {
self.value = other.value.clone();
self.timestamp = other.timestamp;
self.node_id = other.node_id;
true
} else {
false
}
}
fn should_update(&self, timestamp: Timestamp, node_id: &NodeId) -> bool {
timestamp > self.timestamp
|| (timestamp == self.timestamp && node_id.as_u32() > self.node_id.as_u32())
}
}
#[derive(Debug, Clone, Default)]
pub struct GCounter {
counts: BTreeMap<u32, u64>,
}
impl GCounter {
pub fn new() -> Self {
Self {
counts: BTreeMap::new(),
}
}
pub fn value(&self) -> u64 {
self.counts.values().sum()
}
pub fn increment(&mut self, node_id: &NodeId, amount: u64) {
let count = self.counts.entry(node_id.as_u32()).or_insert(0);
*count = count.saturating_add(amount);
}
pub fn node_count(&self, node_id: &NodeId) -> u64 {
self.counts.get(&node_id.as_u32()).copied().unwrap_or(0)
}
pub fn merge(&mut self, other: &GCounter) {
for (&node_id, &count) in &other.counts {
let our_count = self.counts.entry(node_id).or_insert(0);
*our_count = (*our_count).max(count);
}
}
pub fn node_count_total(&self) -> usize {
self.counts.len()
}
pub fn entries(&self) -> impl Iterator<Item = (u32, u64)> + '_ {
self.counts.iter().map(|(&k, &v)| (k, v))
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(4 + self.counts.len() * 12);
buf.extend_from_slice(&(self.counts.len() as u32).to_le_bytes());
for (&node_id, &count) in &self.counts {
buf.extend_from_slice(&node_id.to_le_bytes());
buf.extend_from_slice(&count.to_le_bytes());
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let num_entries = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
if num_entries > MAX_COUNTER_ENTRIES {
return None;
}
if data.len() < 4 + num_entries * 12 {
return None;
}
let mut counts = BTreeMap::new();
let mut offset = 4;
for _ in 0..num_entries {
let node_id = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
let count = u64::from_le_bytes([
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
data[offset + 8],
data[offset + 9],
data[offset + 10],
data[offset + 11],
]);
if node_id != 0 {
counts.insert(node_id, count);
}
offset += 12;
}
Some(Self { counts })
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Position {
pub latitude: f32,
pub longitude: f32,
pub altitude: Option<f32>,
pub accuracy: Option<f32>,
}
impl Position {
pub fn new(latitude: f32, longitude: f32) -> Self {
Self {
latitude,
longitude,
altitude: None,
accuracy: None,
}
}
pub fn with_altitude(mut self, altitude: f32) -> Self {
self.altitude = Some(altitude);
self
}
pub fn with_accuracy(mut self, accuracy: f32) -> Self {
self.accuracy = Some(accuracy);
self
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(20);
buf.extend_from_slice(&self.latitude.to_le_bytes());
buf.extend_from_slice(&self.longitude.to_le_bytes());
let mut flags = 0u8;
if self.altitude.is_some() {
flags |= 0x01;
}
if self.accuracy.is_some() {
flags |= 0x02;
}
buf.push(flags);
if let Some(alt) = self.altitude {
buf.extend_from_slice(&alt.to_le_bytes());
}
if let Some(acc) = self.accuracy {
buf.extend_from_slice(&acc.to_le_bytes());
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 9 {
return None;
}
let latitude = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let longitude = f32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let flags = data[8];
if !latitude.is_finite() || !longitude.is_finite() {
return None;
}
if !(-90.0..=90.0).contains(&latitude) {
return None;
}
if !(-180.0..=180.0).contains(&longitude) {
return None;
}
let mut pos = Self::new(latitude, longitude);
let mut offset = 9;
if flags & 0x01 != 0 {
if data.len() < offset + 4 {
return None;
}
let alt = f32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
if !alt.is_finite() || !(-1000.0..=100000.0).contains(&alt) {
return None;
}
pos.altitude = Some(alt);
offset += 4;
}
if flags & 0x02 != 0 {
if data.len() < offset + 4 {
return None;
}
let acc = f32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
if !acc.is_finite() || acc < 0.0 {
return None;
}
pos.accuracy = Some(acc);
}
Some(pos)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct HealthStatus {
pub battery_percent: u8,
pub heart_rate: Option<u8>,
pub activity: u8,
pub alerts: u8,
}
impl HealthStatus {
pub const ALERT_MAN_DOWN: u8 = 0x01;
pub const ALERT_LOW_BATTERY: u8 = 0x02;
pub const ALERT_OUT_OF_RANGE: u8 = 0x04;
pub const ALERT_CUSTOM_1: u8 = 0x08;
pub fn new(battery_percent: u8) -> Self {
Self {
battery_percent,
heart_rate: None,
activity: 0,
alerts: 0,
}
}
pub fn with_heart_rate(mut self, hr: u8) -> Self {
self.heart_rate = Some(hr);
self
}
pub fn with_activity(mut self, activity: u8) -> Self {
self.activity = activity;
self
}
pub fn set_alert(&mut self, flag: u8) {
self.alerts |= flag;
}
pub fn clear_alert(&mut self, flag: u8) {
self.alerts &= !flag;
}
pub fn has_alert(&self, flag: u8) -> bool {
self.alerts & flag != 0
}
pub fn encode(&self) -> Vec<u8> {
vec![
self.battery_percent,
self.activity,
self.alerts,
self.heart_rate.unwrap_or(0),
]
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let battery_percent = data[0];
let activity = data[1];
let alerts = data[2];
let heart_rate_raw = data[3];
if battery_percent > MAX_BATTERY_PERCENT {
return None;
}
let mut status = Self::new(battery_percent);
status.activity = activity;
status.alerts = alerts;
if heart_rate_raw > 0 {
if !(MIN_HEART_RATE..=MAX_HEART_RATE).contains(&heart_rate_raw) {
return None;
}
status.heart_rate = Some(heart_rate_raw);
}
Some(status)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum PeripheralType {
#[default]
Unknown = 0,
SoldierSensor = 1,
FixedSensor = 2,
Relay = 3,
}
impl PeripheralType {
pub fn from_u8(v: u8) -> Self {
match v {
1 => Self::SoldierSensor,
2 => Self::FixedSensor,
3 => Self::Relay,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum EventType {
#[default]
None = 0,
Ping = 1,
NeedAssist = 2,
Emergency = 3,
Moving = 4,
InPosition = 5,
Ack = 6,
}
impl EventType {
pub fn from_u8(v: u8) -> Self {
match v {
1 => Self::Ping,
2 => Self::NeedAssist,
3 => Self::Emergency,
4 => Self::Moving,
5 => Self::InPosition,
6 => Self::Ack,
_ => Self::None,
}
}
pub fn label(&self) -> &'static str {
match self {
Self::None => "",
Self::Ping => "PING",
Self::NeedAssist => "NEED ASSIST",
Self::Emergency => "EMERGENCY",
Self::Moving => "MOVING",
Self::InPosition => "IN POSITION",
Self::Ack => "ACK",
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PeripheralEvent {
pub event_type: EventType,
pub timestamp: u64,
}
impl PeripheralEvent {
pub fn new(event_type: EventType, timestamp: u64) -> Self {
Self {
event_type,
timestamp,
}
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(9);
buf.push(self.event_type as u8);
buf.extend_from_slice(&self.timestamp.to_le_bytes());
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 9 {
return None;
}
let event_type = EventType::from_u8(data[0]);
let timestamp = u64::from_le_bytes([
data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
]);
if timestamp != 0 && timestamp < MIN_VALID_TIMESTAMP {
return None;
}
Some(Self {
event_type,
timestamp,
})
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct EmergencyEvent {
source_node: u32,
timestamp: u64,
acks: BTreeMap<u32, bool>,
}
impl EmergencyEvent {
pub fn new(source_node: u32, timestamp: u64, known_peers: &[u32]) -> Self {
let mut acks = BTreeMap::new();
acks.insert(source_node, true);
for &peer_id in known_peers {
if peer_id != source_node {
acks.entry(peer_id).or_insert(false);
}
}
Self {
source_node,
timestamp,
acks,
}
}
pub fn source_node(&self) -> u32 {
self.source_node
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn has_acked(&self, node_id: u32) -> bool {
self.acks.get(&node_id).copied().unwrap_or(false)
}
pub fn ack(&mut self, node_id: u32) -> bool {
let entry = self.acks.entry(node_id).or_insert(false);
if !*entry {
*entry = true;
true
} else {
false
}
}
pub fn add_peer(&mut self, node_id: u32) {
self.acks.entry(node_id).or_insert(false);
}
pub fn acked_nodes(&self) -> Vec<u32> {
self.acks
.iter()
.filter(|(_, &acked)| acked)
.map(|(&node_id, _)| node_id)
.collect()
}
pub fn pending_nodes(&self) -> Vec<u32> {
self.acks
.iter()
.filter(|(_, &acked)| !acked)
.map(|(&node_id, _)| node_id)
.collect()
}
pub fn all_nodes(&self) -> Vec<u32> {
self.acks.keys().copied().collect()
}
pub fn all_acked(&self) -> bool {
!self.acks.is_empty() && self.acks.values().all(|&acked| acked)
}
pub fn peer_count(&self) -> usize {
self.acks.len()
}
pub fn ack_count(&self) -> usize {
self.acks.values().filter(|&&acked| acked).count()
}
pub fn merge(&mut self, other: &EmergencyEvent) -> bool {
if self.source_node != other.source_node || self.timestamp != other.timestamp {
if other.timestamp > self.timestamp {
*self = other.clone();
return true;
}
return false;
}
let mut changed = false;
for (&node_id, &other_acked) in &other.acks {
let entry = self.acks.entry(node_id).or_insert(false);
if other_acked && !*entry {
*entry = true;
changed = true;
}
}
changed
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(16 + self.acks.len() * 5);
buf.extend_from_slice(&self.source_node.to_le_bytes());
buf.extend_from_slice(&self.timestamp.to_le_bytes());
buf.extend_from_slice(&(self.acks.len() as u32).to_le_bytes());
for (&node_id, &acked) in &self.acks {
buf.extend_from_slice(&node_id.to_le_bytes());
buf.push(if acked { 1 } else { 0 });
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 16 {
return None;
}
let source_node = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let timestamp = u64::from_le_bytes([
data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
]);
let num_acks = u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
if source_node == 0 {
return None;
}
if timestamp < MIN_VALID_TIMESTAMP {
return None;
}
if num_acks > MAX_EMERGENCY_ACKS {
return None;
}
if data.len() < 16 + num_acks * 5 {
return None;
}
let mut acks = BTreeMap::new();
let mut offset = 16;
for _ in 0..num_acks {
let node_id = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
if node_id != 0 {
let acked = data[offset + 4] != 0;
acks.insert(node_id, acked);
}
offset += 5;
}
Some(Self {
source_node,
timestamp,
acks,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct Peripheral {
pub id: u32,
pub parent_node: u32,
pub peripheral_type: PeripheralType,
pub callsign: [u8; 12],
pub health: HealthStatus,
pub last_event: Option<PeripheralEvent>,
pub location: Option<Position>,
pub timestamp: u64,
}
impl Peripheral {
pub fn new(id: u32, peripheral_type: PeripheralType) -> Self {
Self {
id,
parent_node: 0,
peripheral_type,
callsign: [0u8; 12],
health: HealthStatus::default(),
last_event: None,
location: None,
timestamp: 0,
}
}
pub fn with_callsign(mut self, callsign: &str) -> Self {
let bytes = callsign.as_bytes();
let len = bytes.len().min(12);
self.callsign[..len].copy_from_slice(&bytes[..len]);
self
}
pub fn set_callsign(&mut self, callsign: &str) {
self.callsign = [0u8; 12];
let bytes = callsign.as_bytes();
let len = bytes.len().min(12);
self.callsign[..len].copy_from_slice(&bytes[..len]);
}
pub fn callsign_str(&self) -> &str {
let len = self.callsign.iter().position(|&b| b == 0).unwrap_or(12);
core::str::from_utf8(&self.callsign[..len]).unwrap_or("")
}
pub fn with_parent(mut self, parent_node: u32) -> Self {
self.parent_node = parent_node;
self
}
pub fn with_location(mut self, location: Position) -> Self {
self.location = Some(location);
self
}
pub fn set_location(&mut self, latitude: f32, longitude: f32, altitude: Option<f32>) {
let mut pos = Position::new(latitude, longitude);
if let Some(alt) = altitude {
pos = pos.with_altitude(alt);
}
self.location = Some(pos);
}
pub fn clear_location(&mut self) {
self.location = None;
}
pub fn set_event(&mut self, event_type: EventType, timestamp: u64) {
self.last_event = Some(PeripheralEvent::new(event_type, timestamp));
self.timestamp = timestamp;
}
pub fn clear_event(&mut self) {
self.last_event = None;
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(64);
buf.extend_from_slice(&self.id.to_le_bytes());
buf.extend_from_slice(&self.parent_node.to_le_bytes());
buf.push(self.peripheral_type as u8);
buf.extend_from_slice(&self.callsign);
buf.extend_from_slice(&self.health.encode());
if let Some(ref event) = self.last_event {
buf.push(1); buf.extend_from_slice(&event.encode());
} else {
buf.push(0); }
buf.extend_from_slice(&self.timestamp.to_le_bytes());
if let Some(ref location) = self.location {
buf.push(1); buf.extend_from_slice(&location.encode());
} else {
buf.push(0); }
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 34 {
return None;
}
let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let parent_node = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let peripheral_type = PeripheralType::from_u8(data[8]);
if id == 0 {
return None;
}
let mut callsign = [0u8; 12];
callsign.copy_from_slice(&data[9..21]);
let callsign_len = callsign.iter().position(|&b| b == 0).unwrap_or(12);
if callsign_len > 0 && core::str::from_utf8(&callsign[..callsign_len]).is_err() {
return None;
}
let health = HealthStatus::decode(&data[21..25])?;
let has_event = data[25] != 0;
let (last_event, timestamp_offset) = if has_event {
if data.len() < 43 {
return None;
}
(PeripheralEvent::decode(&data[26..35]), 35)
} else {
(None, 26)
};
if data.len() < timestamp_offset + 8 {
return None;
}
let timestamp = u64::from_le_bytes([
data[timestamp_offset],
data[timestamp_offset + 1],
data[timestamp_offset + 2],
data[timestamp_offset + 3],
data[timestamp_offset + 4],
data[timestamp_offset + 5],
data[timestamp_offset + 6],
data[timestamp_offset + 7],
]);
if timestamp != 0 && timestamp < MIN_VALID_TIMESTAMP {
return None;
}
let location_offset = timestamp_offset + 8;
let location = if data.len() > location_offset {
let has_location = data[location_offset] != 0;
if has_location && data.len() > location_offset + 1 {
Position::decode(&data[location_offset + 1..])
} else {
None
}
} else {
None
};
Some(Self {
id,
parent_node,
peripheral_type,
callsign,
health,
last_event,
location,
timestamp,
})
}
}
#[cfg(feature = "legacy-chat")]
pub const CHAT_MAX_TEXT_LEN: usize = 128;
#[cfg(feature = "legacy-chat")]
pub const CHAT_MAX_SENDER_LEN: usize = 12;
#[cfg(feature = "legacy-chat")]
pub const CHAT_MAX_MESSAGES: usize = 32;
#[cfg(feature = "legacy-chat")]
pub const CHAT_SYNC_LIMIT: usize = 8;
#[cfg(feature = "legacy-chat")]
#[derive(Debug, Clone, PartialEq)]
pub struct ChatMessage {
pub origin_node: u32,
pub timestamp: u64,
sender: [u8; CHAT_MAX_SENDER_LEN],
sender_len: u8,
text: [u8; CHAT_MAX_TEXT_LEN],
text_len: u8,
pub is_broadcast: bool,
pub requires_ack: bool,
pub reply_to_node: u32,
pub reply_to_timestamp: u64,
}
#[cfg(feature = "legacy-chat")]
impl Default for ChatMessage {
fn default() -> Self {
Self {
origin_node: 0,
timestamp: 0,
sender: [0u8; CHAT_MAX_SENDER_LEN],
sender_len: 0,
text: [0u8; CHAT_MAX_TEXT_LEN],
text_len: 0,
is_broadcast: true,
requires_ack: false,
reply_to_node: 0,
reply_to_timestamp: 0,
}
}
}
#[cfg(feature = "legacy-chat")]
impl ChatMessage {
pub fn new(origin_node: u32, timestamp: u64, sender: &str, text: &str) -> Self {
let mut msg = Self {
origin_node,
timestamp,
..Default::default()
};
msg.set_sender(sender);
msg.set_text(text);
msg
}
pub fn set_sender(&mut self, sender: &str) {
let bytes = sender.as_bytes();
let len = bytes.len().min(CHAT_MAX_SENDER_LEN);
self.sender[..len].copy_from_slice(&bytes[..len]);
self.sender_len = len as u8;
}
pub fn sender(&self) -> &str {
core::str::from_utf8(&self.sender[..self.sender_len as usize]).unwrap_or("")
}
pub fn set_text(&mut self, text: &str) {
let bytes = text.as_bytes();
let len = bytes.len().min(CHAT_MAX_TEXT_LEN);
self.text[..len].copy_from_slice(&bytes[..len]);
self.text_len = len as u8;
}
pub fn text(&self) -> &str {
core::str::from_utf8(&self.text[..self.text_len as usize]).unwrap_or("")
}
pub fn set_reply_to(&mut self, node: u32, timestamp: u64) {
self.reply_to_node = node;
self.reply_to_timestamp = timestamp;
}
pub fn is_reply(&self) -> bool {
self.reply_to_node != 0 || self.reply_to_timestamp != 0
}
pub fn message_id(&self) -> u64 {
((self.origin_node as u64) << 32) | (self.timestamp & 0xFFFFFFFF)
}
pub fn encode(&self) -> Vec<u8> {
let size = 4 + 8 + 1 + self.sender_len as usize + 1 + self.text_len as usize + 1 + 4 + 8;
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&self.origin_node.to_le_bytes());
buf.extend_from_slice(&self.timestamp.to_le_bytes());
buf.push(self.sender_len);
buf.extend_from_slice(&self.sender[..self.sender_len as usize]);
buf.push(self.text_len);
buf.extend_from_slice(&self.text[..self.text_len as usize]);
let mut flags = 0u8;
if self.is_broadcast {
flags |= 0x01;
}
if self.requires_ack {
flags |= 0x02;
}
buf.push(flags);
buf.extend_from_slice(&self.reply_to_node.to_le_bytes());
buf.extend_from_slice(&self.reply_to_timestamp.to_le_bytes());
buf
}
pub fn decode(data: &[u8]) -> Option<(Self, usize)> {
if data.len() < 14 {
return None;
}
let origin_node = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let timestamp = u64::from_le_bytes([
data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
]);
if origin_node == 0 {
return None;
}
if timestamp < MIN_VALID_TIMESTAMP {
return None;
}
let sender_len = data[12] as usize;
if sender_len > CHAT_MAX_SENDER_LEN || data.len() < 13 + sender_len + 1 {
return None;
}
if sender_len == 0 {
return None;
}
let mut sender = [0u8; CHAT_MAX_SENDER_LEN];
sender[..sender_len].copy_from_slice(&data[13..13 + sender_len]);
if core::str::from_utf8(&sender[..sender_len]).is_err() {
return None;
}
let text_offset = 13 + sender_len;
let text_len = data[text_offset] as usize;
if text_len > CHAT_MAX_TEXT_LEN || data.len() < text_offset + 1 + text_len + 1 {
return None;
}
let mut text = [0u8; CHAT_MAX_TEXT_LEN];
text[..text_len].copy_from_slice(&data[text_offset + 1..text_offset + 1 + text_len]);
if text_len > 0 && core::str::from_utf8(&text[..text_len]).is_err() {
return None;
}
let flags_offset = text_offset + 1 + text_len;
let flags = data[flags_offset];
let is_broadcast = flags & 0x01 != 0;
let requires_ack = flags & 0x02 != 0;
let mut reply_to_node = 0u32;
let mut reply_to_timestamp = 0u64;
let mut total_len = flags_offset + 1;
if data.len() >= flags_offset + 1 + 12 {
reply_to_node = u32::from_le_bytes([
data[flags_offset + 1],
data[flags_offset + 2],
data[flags_offset + 3],
data[flags_offset + 4],
]);
reply_to_timestamp = u64::from_le_bytes([
data[flags_offset + 5],
data[flags_offset + 6],
data[flags_offset + 7],
data[flags_offset + 8],
data[flags_offset + 9],
data[flags_offset + 10],
data[flags_offset + 11],
data[flags_offset + 12],
]);
total_len = flags_offset + 13;
}
Some((
Self {
origin_node,
timestamp,
sender,
sender_len: sender_len as u8,
text,
text_len: text_len as u8,
is_broadcast,
requires_ack,
reply_to_node,
reply_to_timestamp,
},
total_len,
))
}
}
#[cfg(feature = "legacy-chat")]
#[derive(Debug, Clone, Default)]
pub struct ChatCRDT {
messages: BTreeMap<u64, ChatMessage>,
}
#[cfg(feature = "legacy-chat")]
impl ChatCRDT {
pub fn new() -> Self {
Self {
messages: BTreeMap::new(),
}
}
pub fn add_message(&mut self, message: ChatMessage) -> bool {
let id = message.message_id();
if self.messages.contains_key(&id) {
return false;
}
self.messages.insert(id, message);
self.prune_if_needed();
true
}
pub fn send_message(
&mut self,
origin_node: u32,
timestamp: u64,
sender: &str,
text: &str,
) -> bool {
let msg = ChatMessage::new(origin_node, timestamp, sender, text);
self.add_message(msg)
}
pub fn get_message(&self, origin_node: u32, timestamp: u64) -> Option<&ChatMessage> {
let id = ((origin_node as u64) << 32) | (timestamp & 0xFFFFFFFF);
self.messages.get(&id)
}
pub fn messages(&self) -> impl Iterator<Item = &ChatMessage> {
self.messages.values()
}
pub fn messages_since(&self, since_timestamp: u64) -> impl Iterator<Item = &ChatMessage> {
self.messages
.values()
.filter(move |m| m.timestamp > since_timestamp)
}
pub fn len(&self) -> usize {
self.messages.len()
}
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
pub fn newest_timestamp(&self) -> Option<u64> {
self.messages.values().map(|m| m.timestamp).max()
}
pub fn merge(&mut self, other: &ChatCRDT) -> bool {
let mut changed = false;
for (id, msg) in &other.messages {
if !self.messages.contains_key(id) {
self.messages.insert(*id, msg.clone());
changed = true;
}
}
if changed {
self.prune_if_needed();
}
changed
}
fn prune_if_needed(&mut self) {
while self.messages.len() > CHAT_MAX_MESSAGES {
if let Some(&oldest_id) = self.messages.keys().next() {
self.messages.remove(&oldest_id);
}
}
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&(self.messages.len() as u16).to_le_bytes());
for msg in self.messages.values() {
buf.extend_from_slice(&msg.encode());
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 2 {
return None;
}
let num_messages = u16::from_le_bytes([data[0], data[1]]) as usize;
let mut messages = BTreeMap::new();
let mut offset = 2;
for _ in 0..num_messages {
if offset >= data.len() {
break;
}
if let Some((msg, len)) = ChatMessage::decode(&data[offset..]) {
let id = msg.message_id();
messages.insert(id, msg);
offset += len;
} else {
break;
}
}
Some(Self { messages })
}
pub fn encoded_size(&self) -> usize {
2 + self
.messages
.values()
.map(|m| m.encode().len())
.sum::<usize>()
}
pub fn for_sync(&self) -> Self {
if self.messages.len() <= CHAT_SYNC_LIMIT {
return self.clone();
}
let messages: BTreeMap<u64, ChatMessage> = self
.messages
.iter()
.rev()
.take(CHAT_SYNC_LIMIT)
.map(|(&k, v)| (k, v.clone()))
.collect();
Self { messages }
}
}
#[derive(Debug, Clone)]
pub enum CrdtOperation {
UpdatePosition {
node_id: NodeId,
position: Position,
timestamp: Timestamp,
},
UpdateHealth {
node_id: NodeId,
status: HealthStatus,
timestamp: Timestamp,
},
IncrementCounter {
counter_id: u8,
node_id: NodeId,
amount: u64,
},
UpdateRegister {
key: String,
value: Vec<u8>,
timestamp: Timestamp,
node_id: NodeId,
},
}
impl CrdtOperation {
pub fn size(&self) -> usize {
match self {
CrdtOperation::UpdatePosition { position, .. } => 4 + 8 + position.encode().len(),
CrdtOperation::UpdateHealth { status, .. } => 4 + 8 + status.encode().len(),
CrdtOperation::IncrementCounter { .. } => 1 + 4 + 8,
CrdtOperation::UpdateRegister { key, value, .. } => 4 + 8 + key.len() + value.len(),
}
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
match self {
CrdtOperation::UpdatePosition {
node_id,
position,
timestamp,
} => {
buf.push(0x01); buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
buf.extend_from_slice(×tamp.to_le_bytes());
buf.extend_from_slice(&position.encode());
}
CrdtOperation::UpdateHealth {
node_id,
status,
timestamp,
} => {
buf.push(0x02); buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
buf.extend_from_slice(×tamp.to_le_bytes());
buf.extend_from_slice(&status.encode());
}
CrdtOperation::IncrementCounter {
counter_id,
node_id,
amount,
} => {
buf.push(0x03); buf.push(*counter_id);
buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
buf.extend_from_slice(&amount.to_le_bytes());
}
CrdtOperation::UpdateRegister {
key,
value,
timestamp,
node_id,
} => {
buf.push(0x04); buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
buf.extend_from_slice(×tamp.to_le_bytes());
buf.push(key.len() as u8);
buf.extend_from_slice(key.as_bytes());
buf.extend_from_slice(&(value.len() as u16).to_le_bytes());
buf.extend_from_slice(value);
}
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.is_empty() {
return None;
}
match data[0] {
0x01 => {
if data.len() < 13 {
return None;
}
let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
let timestamp = u64::from_le_bytes([
data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
]);
let position = Position::decode(&data[13..])?;
Some(CrdtOperation::UpdatePosition {
node_id,
position,
timestamp,
})
}
0x02 => {
if data.len() < 13 {
return None;
}
let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
let timestamp = u64::from_le_bytes([
data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
]);
let status = HealthStatus::decode(&data[13..])?;
Some(CrdtOperation::UpdateHealth {
node_id,
status,
timestamp,
})
}
0x03 => {
if data.len() < 14 {
return None;
}
let counter_id = data[1];
let node_id = NodeId::new(u32::from_le_bytes([data[2], data[3], data[4], data[5]]));
let amount = u64::from_le_bytes([
data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13],
]);
Some(CrdtOperation::IncrementCounter {
counter_id,
node_id,
amount,
})
}
0x04 => {
if data.len() < 14 {
return None;
}
let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
let timestamp = u64::from_le_bytes([
data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
]);
let key_len = data[13] as usize;
if data.len() < 14 + key_len + 2 {
return None;
}
let key = core::str::from_utf8(&data[14..14 + key_len])
.ok()?
.to_string();
let value_len =
u16::from_le_bytes([data[14 + key_len], data[15 + key_len]]) as usize;
if data.len() < 16 + key_len + value_len {
return None;
}
let value = data[16 + key_len..16 + key_len + value_len].to_vec();
Some(CrdtOperation::UpdateRegister {
key,
value,
timestamp,
node_id,
})
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_TIMESTAMP: u64 = 1705276800000;
#[test]
fn test_lww_register_basic() {
let mut reg = LwwRegister::new(42u32, 100, NodeId::new(1));
assert_eq!(*reg.get(), 42);
assert_eq!(reg.timestamp(), 100);
assert!(reg.set(99, 200, NodeId::new(2)));
assert_eq!(*reg.get(), 99);
assert!(!reg.set(50, 150, NodeId::new(3)));
assert_eq!(*reg.get(), 99);
}
#[test]
fn test_lww_register_tiebreak() {
let mut reg = LwwRegister::new(1u32, 100, NodeId::new(1));
assert!(reg.set(2, 100, NodeId::new(2)));
assert_eq!(*reg.get(), 2);
assert!(!reg.set(3, 100, NodeId::new(1)));
assert_eq!(*reg.get(), 2);
}
#[test]
fn test_lww_register_merge() {
let mut reg1 = LwwRegister::new(1u32, 100, NodeId::new(1));
let reg2 = LwwRegister::new(2u32, 200, NodeId::new(2));
assert!(reg1.merge(®2));
assert_eq!(*reg1.get(), 2);
}
#[test]
fn test_gcounter_basic() {
let mut counter = GCounter::new();
let node1 = NodeId::new(1);
let node2 = NodeId::new(2);
counter.increment(&node1, 5);
counter.increment(&node2, 3);
counter.increment(&node1, 2);
assert_eq!(counter.value(), 10);
assert_eq!(counter.node_count(&node1), 7);
assert_eq!(counter.node_count(&node2), 3);
}
#[test]
fn test_gcounter_merge() {
let mut counter1 = GCounter::new();
let mut counter2 = GCounter::new();
let node1 = NodeId::new(1);
let node2 = NodeId::new(2);
counter1.increment(&node1, 5);
counter2.increment(&node1, 3);
counter2.increment(&node2, 4);
counter1.merge(&counter2);
assert_eq!(counter1.value(), 9); assert_eq!(counter1.node_count(&node1), 5);
assert_eq!(counter1.node_count(&node2), 4);
}
#[test]
fn test_gcounter_encode_decode() {
let mut counter = GCounter::new();
counter.increment(&NodeId::new(1), 5);
counter.increment(&NodeId::new(2), 10);
let encoded = counter.encode();
let decoded = GCounter::decode(&encoded).unwrap();
assert_eq!(decoded.value(), counter.value());
assert_eq!(decoded.node_count(&NodeId::new(1)), 5);
assert_eq!(decoded.node_count(&NodeId::new(2)), 10);
}
#[test]
fn test_position_encode_decode() {
let pos = Position::new(37.7749, -122.4194)
.with_altitude(100.0)
.with_accuracy(5.0);
let encoded = pos.encode();
let decoded = Position::decode(&encoded).unwrap();
assert_eq!(decoded.latitude, pos.latitude);
assert_eq!(decoded.longitude, pos.longitude);
assert_eq!(decoded.altitude, pos.altitude);
assert_eq!(decoded.accuracy, pos.accuracy);
}
#[test]
fn test_position_minimal_encode() {
let pos = Position::new(0.0, 0.0);
let encoded = pos.encode();
assert_eq!(encoded.len(), 9);
let pos_with_alt = Position::new(0.0, 0.0).with_altitude(0.0);
let encoded_alt = pos_with_alt.encode();
assert_eq!(encoded_alt.len(), 13);
}
#[test]
fn test_health_status() {
let mut status = HealthStatus::new(85).with_heart_rate(72).with_activity(1);
assert_eq!(status.battery_percent, 85);
assert_eq!(status.heart_rate, Some(72));
assert!(!status.has_alert(HealthStatus::ALERT_MAN_DOWN));
status.set_alert(HealthStatus::ALERT_MAN_DOWN);
assert!(status.has_alert(HealthStatus::ALERT_MAN_DOWN));
let encoded = status.encode();
let decoded = HealthStatus::decode(&encoded).unwrap();
assert_eq!(decoded.battery_percent, 85);
assert_eq!(decoded.heart_rate, Some(72));
assert!(decoded.has_alert(HealthStatus::ALERT_MAN_DOWN));
}
#[test]
fn test_health_status_decode_unknown_activity_preserves_peripheral() {
let status = HealthStatus::new(87).with_activity(4);
let encoded = status.encode();
let decoded = HealthStatus::decode(&encoded)
.expect("activity=4 must roundtrip without rejecting the peripheral");
assert_eq!(decoded.battery_percent, 87);
assert_eq!(decoded.activity, 4);
}
#[test]
fn test_crdt_operation_position() {
let op = CrdtOperation::UpdatePosition {
node_id: NodeId::new(0x1234),
position: Position::new(37.0, -122.0),
timestamp: 1000,
};
let encoded = op.encode();
let decoded = CrdtOperation::decode(&encoded).unwrap();
if let CrdtOperation::UpdatePosition {
node_id,
position,
timestamp,
} = decoded
{
assert_eq!(node_id.as_u32(), 0x1234);
assert_eq!(timestamp, 1000);
assert_eq!(position.latitude, 37.0);
} else {
panic!("Wrong operation type");
}
}
#[test]
fn test_crdt_operation_counter() {
let op = CrdtOperation::IncrementCounter {
counter_id: 1,
node_id: NodeId::new(0x5678),
amount: 42,
};
let encoded = op.encode();
let decoded = CrdtOperation::decode(&encoded).unwrap();
if let CrdtOperation::IncrementCounter {
counter_id,
node_id,
amount,
} = decoded
{
assert_eq!(counter_id, 1);
assert_eq!(node_id.as_u32(), 0x5678);
assert_eq!(amount, 42);
} else {
panic!("Wrong operation type");
}
}
#[test]
fn test_crdt_operation_size() {
let pos_op = CrdtOperation::UpdatePosition {
node_id: NodeId::new(1),
position: Position::new(0.0, 0.0),
timestamp: 0,
};
assert!(pos_op.size() > 0);
let counter_op = CrdtOperation::IncrementCounter {
counter_id: 0,
node_id: NodeId::new(1),
amount: 1,
};
assert_eq!(counter_op.size(), 13);
}
#[test]
fn test_peripheral_type_from_u8() {
assert_eq!(PeripheralType::from_u8(0), PeripheralType::Unknown);
assert_eq!(PeripheralType::from_u8(1), PeripheralType::SoldierSensor);
assert_eq!(PeripheralType::from_u8(2), PeripheralType::FixedSensor);
assert_eq!(PeripheralType::from_u8(3), PeripheralType::Relay);
assert_eq!(PeripheralType::from_u8(99), PeripheralType::Unknown);
}
#[test]
fn test_event_type_from_u8() {
assert_eq!(EventType::from_u8(0), EventType::None);
assert_eq!(EventType::from_u8(1), EventType::Ping);
assert_eq!(EventType::from_u8(2), EventType::NeedAssist);
assert_eq!(EventType::from_u8(3), EventType::Emergency);
assert_eq!(EventType::from_u8(4), EventType::Moving);
assert_eq!(EventType::from_u8(5), EventType::InPosition);
assert_eq!(EventType::from_u8(6), EventType::Ack);
assert_eq!(EventType::from_u8(99), EventType::None);
}
#[test]
fn test_event_type_labels() {
assert_eq!(EventType::None.label(), "");
assert_eq!(EventType::Emergency.label(), "EMERGENCY");
assert_eq!(EventType::Ping.label(), "PING");
}
#[test]
fn test_peripheral_event_encode_decode() {
let event = PeripheralEvent::new(EventType::Emergency, TEST_TIMESTAMP);
let encoded = event.encode();
assert_eq!(encoded.len(), 9);
let decoded = PeripheralEvent::decode(&encoded).unwrap();
assert_eq!(decoded.event_type, EventType::Emergency);
assert_eq!(decoded.timestamp, TEST_TIMESTAMP);
}
#[test]
fn test_peripheral_new() {
let peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor);
assert_eq!(peripheral.id, 0x12345678);
assert_eq!(peripheral.peripheral_type, PeripheralType::SoldierSensor);
assert_eq!(peripheral.parent_node, 0);
assert!(peripheral.last_event.is_none());
}
#[test]
fn test_peripheral_with_callsign() {
let peripheral = Peripheral::new(1, PeripheralType::SoldierSensor).with_callsign("ALPHA-1");
assert_eq!(peripheral.callsign_str(), "ALPHA-1");
let peripheral2 = Peripheral::new(2, PeripheralType::SoldierSensor)
.with_callsign("THIS_IS_A_VERY_LONG_CALLSIGN");
assert_eq!(peripheral2.callsign_str(), "THIS_IS_A_VE");
}
#[test]
fn test_peripheral_set_event() {
let mut peripheral = Peripheral::new(1, PeripheralType::SoldierSensor);
peripheral.set_event(EventType::Emergency, TEST_TIMESTAMP);
assert!(peripheral.last_event.is_some());
let event = peripheral.last_event.as_ref().unwrap();
assert_eq!(event.event_type, EventType::Emergency);
assert_eq!(event.timestamp, TEST_TIMESTAMP);
assert_eq!(peripheral.timestamp, TEST_TIMESTAMP);
peripheral.clear_event();
assert!(peripheral.last_event.is_none());
}
#[test]
fn test_peripheral_encode_decode_without_event() {
let peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor)
.with_callsign("BRAVO-2")
.with_parent(0x11223344);
let encoded = peripheral.encode();
assert_eq!(encoded.len(), 35);
let decoded = Peripheral::decode(&encoded).unwrap();
assert_eq!(decoded.id, 0xAABBCCDD);
assert_eq!(decoded.parent_node, 0x11223344);
assert_eq!(decoded.peripheral_type, PeripheralType::SoldierSensor);
assert_eq!(decoded.callsign_str(), "BRAVO-2");
assert!(decoded.last_event.is_none());
assert!(decoded.location.is_none());
}
#[test]
fn test_peripheral_encode_decode_with_event() {
let mut peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor)
.with_callsign("CHARLIE")
.with_parent(0x87654321);
peripheral.health = HealthStatus::new(85);
peripheral.set_event(EventType::NeedAssist, TEST_TIMESTAMP);
let encoded = peripheral.encode();
assert_eq!(encoded.len(), 44);
let decoded = Peripheral::decode(&encoded).unwrap();
assert_eq!(decoded.id, 0x12345678);
assert_eq!(decoded.parent_node, 0x87654321);
assert_eq!(decoded.callsign_str(), "CHARLIE");
assert_eq!(decoded.health.battery_percent, 85);
assert!(decoded.last_event.is_some());
let event = decoded.last_event.as_ref().unwrap();
assert_eq!(event.event_type, EventType::NeedAssist);
assert_eq!(event.timestamp, TEST_TIMESTAMP);
assert!(decoded.location.is_none());
}
#[test]
fn test_peripheral_encode_decode_with_location() {
let location = Position::new(37.7749, -122.4194).with_altitude(10.0);
let peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor)
.with_callsign("DELTA")
.with_location(location);
let encoded = peripheral.encode();
assert_eq!(encoded.len(), 48);
let decoded = Peripheral::decode(&encoded).unwrap();
assert_eq!(decoded.id, 0x12345678);
assert_eq!(decoded.callsign_str(), "DELTA");
assert!(decoded.location.is_some());
let loc = decoded.location.unwrap();
assert!((loc.latitude - 37.7749).abs() < 0.0001);
assert!((loc.longitude - (-122.4194)).abs() < 0.0001);
assert!(loc.altitude.is_some());
assert!((loc.altitude.unwrap() - 10.0).abs() < 1.0);
}
#[test]
fn test_peripheral_decode_invalid_data() {
assert!(Peripheral::decode(&[0u8; 10]).is_none());
let mut data = vec![0u8; 34];
data[25] = 0; assert!(Peripheral::decode(&data).is_none());
data[0..4].copy_from_slice(&1u32.to_le_bytes()); assert!(Peripheral::decode(&data).is_some());
data[25] = 1; assert!(Peripheral::decode(&data).is_none());
}
#[test]
fn test_emergency_event_new() {
let peers = vec![0x22222222, 0x33333333];
let event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
assert_eq!(event.source_node(), 0x11111111);
assert_eq!(event.timestamp(), TEST_TIMESTAMP);
assert_eq!(event.peer_count(), 3);
assert!(event.has_acked(0x11111111));
assert!(!event.has_acked(0x22222222));
assert!(!event.has_acked(0x33333333));
}
#[test]
fn test_emergency_event_ack() {
let peers = vec![0x22222222, 0x33333333];
let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
assert_eq!(event.ack_count(), 1); assert!(!event.all_acked());
assert!(event.ack(0x22222222)); assert_eq!(event.ack_count(), 2);
assert!(!event.all_acked());
assert!(!event.ack(0x22222222));
assert!(event.ack(0x33333333));
assert_eq!(event.ack_count(), 3);
assert!(event.all_acked());
}
#[test]
fn test_emergency_event_pending_nodes() {
let peers = vec![0x22222222, 0x33333333];
let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
let pending = event.pending_nodes();
assert_eq!(pending.len(), 2);
assert!(pending.contains(&0x22222222));
assert!(pending.contains(&0x33333333));
event.ack(0x22222222);
let pending = event.pending_nodes();
assert_eq!(pending.len(), 1);
assert!(pending.contains(&0x33333333));
}
#[test]
fn test_emergency_event_encode_decode() {
let peers = vec![0x22222222, 0x33333333];
let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
event.ack(0x22222222);
let encoded = event.encode();
let decoded = EmergencyEvent::decode(&encoded).unwrap();
assert_eq!(decoded.source_node(), 0x11111111);
assert_eq!(decoded.timestamp(), TEST_TIMESTAMP);
assert!(decoded.has_acked(0x11111111));
assert!(decoded.has_acked(0x22222222));
assert!(!decoded.has_acked(0x33333333));
}
#[test]
fn test_emergency_event_merge_same_event() {
let peers = vec![0x22222222, 0x33333333];
let mut event1 = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
let mut event2 = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
event1.ack(0x22222222);
event2.ack(0x33333333);
let changed = event1.merge(&event2);
assert!(changed);
assert!(event1.has_acked(0x22222222));
assert!(event1.has_acked(0x33333333));
assert!(event1.all_acked());
}
#[test]
fn test_emergency_event_merge_different_events() {
let mut old_event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &[0x22222222]);
old_event.ack(0x22222222);
let new_event =
EmergencyEvent::new(0x33333333, TEST_TIMESTAMP + 1000, &[0x11111111, 0x22222222]);
let changed = old_event.merge(&new_event);
assert!(changed);
assert_eq!(old_event.source_node(), 0x33333333);
assert_eq!(old_event.timestamp(), TEST_TIMESTAMP + 1000);
assert!(!old_event.has_acked(0x22222222));
}
#[test]
fn test_emergency_event_merge_older_event_ignored() {
let mut current = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP + 1000, &[0x22222222]);
let older = EmergencyEvent::new(0x33333333, TEST_TIMESTAMP, &[0x11111111]);
let changed = current.merge(&older);
assert!(!changed);
assert_eq!(current.source_node(), 0x11111111);
assert_eq!(current.timestamp(), TEST_TIMESTAMP + 1000);
}
#[test]
fn test_emergency_event_add_peer() {
let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &[]);
event.add_peer(0x22222222);
assert!(!event.has_acked(0x22222222));
assert_eq!(event.peer_count(), 2);
event.ack(0x22222222);
event.add_peer(0x22222222);
assert!(event.has_acked(0x22222222)); }
#[test]
fn test_emergency_event_decode_invalid() {
assert!(EmergencyEvent::decode(&[0u8; 10]).is_none());
let mut data = vec![0u8; 16];
data[12] = 5; assert!(EmergencyEvent::decode(&data).is_none());
}
#[cfg(feature = "legacy-chat")]
mod chat_tests {
use super::*;
#[test]
fn test_chat_message_new() {
let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "ALPHA-1", "Hello mesh!");
assert_eq!(msg.origin_node, 0x12345678);
assert_eq!(msg.timestamp, TEST_TIMESTAMP);
assert_eq!(msg.sender(), "ALPHA-1");
assert_eq!(msg.text(), "Hello mesh!");
assert!(msg.is_broadcast);
assert!(!msg.requires_ack);
assert!(!msg.is_reply());
}
#[test]
fn test_chat_message_reply_to() {
let mut msg =
ChatMessage::new(0x12345678, TEST_TIMESTAMP + 1000, "BRAVO", "Roger that");
msg.set_reply_to(0xAABBCCDD, TEST_TIMESTAMP);
assert!(msg.is_reply());
assert_eq!(msg.reply_to_node, 0xAABBCCDD);
assert_eq!(msg.reply_to_timestamp, TEST_TIMESTAMP);
}
#[test]
fn test_chat_message_truncation() {
let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "VERY_LONG_CALLSIGN", "Hi");
assert_eq!(msg.sender(), "VERY_LONG_CA");
let long_text = "A".repeat(200);
let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "X", &long_text);
assert_eq!(msg.text().len(), 128);
}
#[test]
fn test_chat_message_id() {
let msg = ChatMessage::new(0x12345678, 0x18D4A51_ABCDEF01, "X", "Y");
let id = msg.message_id();
assert_eq!(id, (0x12345678u64 << 32) | 0xABCDEF01);
}
#[test]
fn test_chat_message_encode_decode() {
let mut msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "CHARLIE", "Test message");
msg.is_broadcast = true;
msg.requires_ack = true;
msg.set_reply_to(0xAABBCCDD, TEST_TIMESTAMP - 1000);
let encoded = msg.encode();
let (decoded, len) = ChatMessage::decode(&encoded).unwrap();
assert_eq!(len, encoded.len());
assert_eq!(decoded.origin_node, 0x12345678);
assert_eq!(decoded.timestamp, TEST_TIMESTAMP);
assert_eq!(decoded.sender(), "CHARLIE");
assert_eq!(decoded.text(), "Test message");
assert!(decoded.is_broadcast);
assert!(decoded.requires_ack);
assert_eq!(decoded.reply_to_node, 0xAABBCCDD);
assert_eq!(decoded.reply_to_timestamp, TEST_TIMESTAMP - 1000);
}
#[test]
fn test_chat_message_decode_empty_text() {
let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "ACK-NODE", "");
let encoded = msg.encode();
let (decoded, _) = ChatMessage::decode(&encoded).unwrap();
assert_eq!(decoded.sender(), "ACK-NODE");
assert_eq!(decoded.text(), "");
}
#[test]
fn test_chat_message_decode_rejects_zero_origin() {
let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "TEST", "msg");
let mut encoded = msg.encode();
encoded[0] = 0;
encoded[1] = 0;
encoded[2] = 0;
encoded[3] = 0;
assert!(ChatMessage::decode(&encoded).is_none());
}
#[test]
fn test_chat_message_decode_rejects_old_timestamp() {
let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "TEST", "msg");
let mut encoded = msg.encode();
let old_ts: u64 = 1000;
encoded[4..12].copy_from_slice(&old_ts.to_le_bytes());
assert!(ChatMessage::decode(&encoded).is_none());
}
#[test]
fn test_chat_message_decode_rejects_empty_sender() {
let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "X", "msg");
let mut encoded = msg.encode();
encoded[12] = 0;
let mut raw = Vec::new();
raw.extend_from_slice(&0x12345678u32.to_le_bytes()); raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); raw.push(0); raw.push(3); raw.extend_from_slice(b"msg"); raw.push(0x01);
assert!(ChatMessage::decode(&raw).is_none());
}
#[test]
fn test_chat_message_decode_rejects_invalid_utf8_sender() {
let mut raw = Vec::new();
raw.extend_from_slice(&0x12345678u32.to_le_bytes()); raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); raw.push(4); raw.extend_from_slice(&[0x66, 0x59, 0xFF, 0xFE]); raw.push(3); raw.extend_from_slice(b"msg"); raw.push(0x01); raw.extend_from_slice(&0u32.to_le_bytes()); raw.extend_from_slice(&0u64.to_le_bytes());
assert!(ChatMessage::decode(&raw).is_none());
}
#[test]
fn test_chat_message_decode_rejects_invalid_utf8_text() {
let mut raw = Vec::new();
raw.extend_from_slice(&0x12345678u32.to_le_bytes()); raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); raw.push(4); raw.extend_from_slice(b"TEST"); raw.push(4); raw.extend_from_slice(&[0x80, 0x81, 0x82, 0x83]); raw.push(0x01); raw.extend_from_slice(&0u32.to_le_bytes()); raw.extend_from_slice(&0u64.to_le_bytes());
assert!(ChatMessage::decode(&raw).is_none());
}
#[test]
fn test_chat_message_decode_accepts_valid_utf8() {
let mut raw = Vec::new();
raw.extend_from_slice(&0x12345678u32.to_le_bytes()); raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); raw.push(6); raw.extend_from_slice("TËST".as_bytes()); let sender_bytes = "TËST1".as_bytes(); raw[12] = sender_bytes.len() as u8;
raw.truncate(13);
raw.extend_from_slice(sender_bytes);
raw.push(4); raw.extend_from_slice(b"test"); raw.push(0x01); raw.extend_from_slice(&0u32.to_le_bytes()); raw.extend_from_slice(&0u64.to_le_bytes());
let result = ChatMessage::decode(&raw);
assert!(result.is_some());
let (msg, _) = result.unwrap();
assert_eq!(msg.sender(), "TËST1");
}
#[test]
fn test_chat_crdt_new() {
let chat = ChatCRDT::new();
assert!(chat.is_empty());
assert_eq!(chat.len(), 0);
}
#[test]
fn test_chat_crdt_add_message() {
let mut chat = ChatCRDT::new();
let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello");
assert!(chat.add_message(msg.clone()));
assert_eq!(chat.len(), 1);
assert!(!chat.add_message(msg));
assert_eq!(chat.len(), 1);
}
#[test]
fn test_chat_crdt_send_message() {
let mut chat = ChatCRDT::new();
assert!(chat.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "First"));
assert!(chat.send_message(0x2, TEST_TIMESTAMP + 1, "BRAVO", "Second"));
assert_eq!(chat.len(), 2);
assert!(!chat.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Duplicate"));
assert_eq!(chat.len(), 2);
}
#[test]
fn test_chat_crdt_get_message() {
let mut chat = ChatCRDT::new();
chat.send_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Test");
let msg = chat.get_message(0x12345678, TEST_TIMESTAMP);
assert!(msg.is_some());
assert_eq!(msg.unwrap().text(), "Test");
assert!(chat.get_message(0x99999999, TEST_TIMESTAMP).is_none());
}
#[test]
fn test_chat_crdt_merge() {
let mut chat1 = ChatCRDT::new();
let mut chat2 = ChatCRDT::new();
chat1.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "From 1");
chat2.send_message(0x2, TEST_TIMESTAMP + 1, "BRAVO", "From 2");
let changed = chat1.merge(&chat2);
assert!(changed);
assert_eq!(chat1.len(), 2);
let changed = chat1.merge(&chat2);
assert!(!changed);
assert_eq!(chat1.len(), 2);
}
#[test]
fn test_chat_crdt_merge_duplicates() {
let mut chat1 = ChatCRDT::new();
let mut chat2 = ChatCRDT::new();
chat1.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Same message");
chat2.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Same message");
chat1.merge(&chat2);
assert_eq!(chat1.len(), 1);
}
#[test]
fn test_chat_crdt_pruning() {
let mut chat = ChatCRDT::new();
for i in 0..(CHAT_MAX_MESSAGES + 10) {
chat.send_message(i as u32 + 1, TEST_TIMESTAMP + i as u64, "X", "Y");
}
assert_eq!(chat.len(), CHAT_MAX_MESSAGES);
assert!(chat.get_message(1, TEST_TIMESTAMP).is_none());
assert!(chat.get_message(10, TEST_TIMESTAMP + 9).is_none());
assert!(chat.get_message(11, TEST_TIMESTAMP + 10).is_some());
}
#[test]
fn test_chat_crdt_encode_decode() {
let mut chat = ChatCRDT::new();
chat.send_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "First message");
chat.send_message(0xAABBCCDD, TEST_TIMESTAMP + 1000, "BRAVO", "Second message");
let encoded = chat.encode();
let decoded = ChatCRDT::decode(&encoded).unwrap();
assert_eq!(decoded.len(), 2);
assert!(decoded.get_message(0x12345678, TEST_TIMESTAMP).is_some());
assert!(decoded
.get_message(0xAABBCCDD, TEST_TIMESTAMP + 1000)
.is_some());
}
#[test]
fn test_chat_crdt_messages_since() {
let mut chat = ChatCRDT::new();
chat.send_message(0x1, TEST_TIMESTAMP, "A", "Old");
chat.send_message(0x2, TEST_TIMESTAMP + 1000, "B", "Mid");
chat.send_message(0x3, TEST_TIMESTAMP + 2000, "C", "New");
let recent: Vec<_> = chat.messages_since(TEST_TIMESTAMP + 500).collect();
assert_eq!(recent.len(), 2);
}
#[test]
fn test_chat_crdt_newest_timestamp() {
let mut chat = ChatCRDT::new();
assert!(chat.newest_timestamp().is_none());
chat.send_message(0x1, TEST_TIMESTAMP, "A", "1");
assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP));
chat.send_message(0x2, TEST_TIMESTAMP + 2000, "B", "2");
assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP + 2000));
chat.send_message(0x3, TEST_TIMESTAMP + 1000, "C", "3"); assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP + 2000));
}
#[test]
fn test_chat_crdt_decode_skips_invalid_messages() {
let mut valid_chat = ChatCRDT::new();
valid_chat.send_message(0x12345678, TEST_TIMESTAMP, "VALID", "Good message");
let encoded = valid_chat.encode();
let decoded = ChatCRDT::decode(&encoded).unwrap();
assert_eq!(decoded.len(), 1);
assert!(decoded.get_message(0x12345678, TEST_TIMESTAMP).is_some());
}
#[test]
fn test_chat_crdt_decode_handles_truncated_data() {
let mut chat = ChatCRDT::new();
chat.send_message(0x12345678, TEST_TIMESTAMP, "TEST", "Message");
let encoded = chat.encode();
let truncated = &encoded[..2];
let decoded = ChatCRDT::decode(truncated);
assert!(decoded.is_some());
assert_eq!(decoded.unwrap().len(), 0); }
} }