1#[cfg(not(feature = "std"))]
43use alloc::vec::Vec;
44
45#[cfg(feature = "legacy-chat")]
46use crate::sync::crdt::ChatCRDT;
47use crate::sync::crdt::{EmergencyEvent, EventType, GCounter, Peripheral, PeripheralEvent};
48use crate::NodeId;
49
50pub const EXTENDED_MARKER: u8 = 0xAB;
52
53pub const EMERGENCY_MARKER: u8 = 0xAC;
55
56pub const CHAT_MARKER: u8 = 0xAD;
67
68pub const ENCRYPTED_MARKER: u8 = 0xAE;
82
83pub const PEER_E2EE_MARKER: u8 = 0xAF;
98
99pub const KEY_EXCHANGE_MARKER: u8 = 0xB0;
110
111pub const RELAY_ENVELOPE_MARKER: u8 = 0xB1;
127
128pub const DELTA_DOCUMENT_MARKER: u8 = 0xB2;
143
144pub const TRANSLATOR_FRAME_MARKER: u8 = 0xB6;
161
162pub const TRANSLATOR_RESERVED_MARKER_START: u8 = 0xB7;
168pub const TRANSLATOR_RESERVED_MARKER_END: u8 = 0xBF;
170
171pub const MIN_DOCUMENT_SIZE: usize = 8;
173
174pub const MAX_MESH_SIZE: usize = 20;
180
181pub const TARGET_DOCUMENT_SIZE: usize = 244;
185
186pub const MAX_DOCUMENT_SIZE: usize = 512;
190
191#[derive(Debug, Clone)]
197pub struct PeatDocument {
198 pub version: u32,
200
201 pub node_id: NodeId,
203
204 pub counter: GCounter,
206
207 pub peripheral: Option<Peripheral>,
209
210 pub emergency: Option<EmergencyEvent>,
212
213 #[cfg(feature = "legacy-chat")]
219 pub chat: Option<ChatCRDT>,
220}
221
222impl Default for PeatDocument {
223 fn default() -> Self {
224 Self {
225 version: 1,
226 node_id: NodeId::default(),
227 counter: GCounter::new(),
228 peripheral: None,
229 emergency: None,
230 #[cfg(feature = "legacy-chat")]
231 chat: None,
232 }
233 }
234}
235
236impl PeatDocument {
237 pub fn new(node_id: NodeId) -> Self {
239 Self {
240 version: 1,
241 node_id,
242 counter: GCounter::new(),
243 peripheral: None,
244 emergency: None,
245 #[cfg(feature = "legacy-chat")]
246 chat: None,
247 }
248 }
249
250 pub fn with_peripheral(mut self, peripheral: Peripheral) -> Self {
252 self.peripheral = Some(peripheral);
253 self
254 }
255
256 pub fn with_emergency(mut self, emergency: EmergencyEvent) -> Self {
258 self.emergency = Some(emergency);
259 self
260 }
261
262 #[cfg(feature = "legacy-chat")]
264 pub fn with_chat(mut self, chat: ChatCRDT) -> Self {
265 self.chat = Some(chat);
266 self
267 }
268
269 pub fn increment_version(&mut self) {
271 self.version = self.version.wrapping_add(1);
272 }
273
274 pub fn increment_counter(&mut self) {
276 self.counter.increment(&self.node_id, 1);
277 self.increment_version();
278 }
279
280 pub fn set_event(&mut self, event_type: EventType, timestamp: u64) {
282 if let Some(ref mut peripheral) = self.peripheral {
283 peripheral.set_event(event_type, timestamp);
284 self.increment_counter();
285 }
286 }
287
288 pub fn clear_event(&mut self) {
290 if let Some(ref mut peripheral) = self.peripheral {
291 peripheral.clear_event();
292 self.increment_version();
293 }
294 }
295
296 pub fn set_emergency(&mut self, source_node: u32, timestamp: u64, known_peers: &[u32]) {
301 self.emergency = Some(EmergencyEvent::new(source_node, timestamp, known_peers));
302 self.increment_counter();
303 }
304
305 pub fn ack_emergency(&mut self, node_id: u32) -> bool {
309 if let Some(ref mut emergency) = self.emergency {
310 if emergency.ack(node_id) {
311 self.increment_version();
312 return true;
313 }
314 }
315 false
316 }
317
318 pub fn clear_emergency(&mut self) {
320 if self.emergency.is_some() {
321 self.emergency = None;
322 self.increment_version();
323 }
324 }
325
326 pub fn get_emergency(&self) -> Option<&EmergencyEvent> {
328 self.emergency.as_ref()
329 }
330
331 pub fn has_emergency(&self) -> bool {
333 self.emergency.is_some()
334 }
335
336 #[cfg(feature = "legacy-chat")]
340 pub fn get_chat(&self) -> Option<&ChatCRDT> {
341 self.chat.as_ref()
342 }
343
344 #[cfg(feature = "legacy-chat")]
346 pub fn get_or_create_chat(&mut self) -> &mut ChatCRDT {
347 if self.chat.is_none() {
348 self.chat = Some(ChatCRDT::new());
349 }
350 self.chat.as_mut().unwrap()
351 }
352
353 #[cfg(feature = "legacy-chat")]
357 pub fn add_chat_message(
358 &mut self,
359 origin_node: u32,
360 timestamp: u64,
361 sender: &str,
362 text: &str,
363 ) -> bool {
364 use crate::sync::crdt::ChatMessage;
365
366 let mut msg = ChatMessage::new(origin_node, timestamp, sender, text);
367 msg.is_broadcast = true;
368
369 let chat = self.get_or_create_chat();
370 if chat.add_message(msg) {
371 self.increment_counter();
372 true
373 } else {
374 false
375 }
376 }
377
378 #[cfg(feature = "legacy-chat")]
380 pub fn add_chat_reply(
381 &mut self,
382 origin_node: u32,
383 timestamp: u64,
384 sender: &str,
385 text: &str,
386 reply_to_node: u32,
387 reply_to_timestamp: u64,
388 ) -> bool {
389 use crate::sync::crdt::ChatMessage;
390
391 let mut msg = ChatMessage::new(origin_node, timestamp, sender, text);
392 msg.is_broadcast = true;
393 msg.set_reply_to(reply_to_node, reply_to_timestamp);
394
395 let chat = self.get_or_create_chat();
396 if chat.add_message(msg) {
397 self.increment_counter();
398 true
399 } else {
400 false
401 }
402 }
403
404 #[cfg(feature = "legacy-chat")]
406 pub fn has_chat(&self) -> bool {
407 self.chat.as_ref().is_some_and(|c| !c.is_empty())
408 }
409
410 #[cfg(feature = "legacy-chat")]
412 pub fn chat_count(&self) -> usize {
413 self.chat.as_ref().map_or(0, |c| c.len())
414 }
415
416 pub fn merge(&mut self, other: &PeatDocument) -> bool {
420 let mut changed = false;
421
422 let old_value = self.counter.value();
424 self.counter.merge(&other.counter);
425 if self.counter.value() != old_value {
426 changed = true;
427 }
428
429 if let Some(ref other_emergency) = other.emergency {
431 match &mut self.emergency {
432 Some(ref mut our_emergency) => {
433 if our_emergency.merge(other_emergency) {
434 changed = true;
435 }
436 }
437 None => {
438 self.emergency = Some(other_emergency.clone());
439 changed = true;
440 }
441 }
442 }
443
444 #[cfg(feature = "legacy-chat")]
446 if let Some(ref other_chat) = other.chat {
447 match &mut self.chat {
448 Some(ref mut our_chat) => {
449 if our_chat.merge(other_chat) {
450 changed = true;
451 }
452 }
453 None => {
454 if !other_chat.is_empty() {
455 self.chat = Some(other_chat.clone());
456 changed = true;
457 }
458 }
459 }
460 }
461
462 if changed {
463 self.increment_version();
464 }
465 changed
466 }
467
468 pub fn current_event(&self) -> Option<EventType> {
470 self.peripheral
471 .as_ref()
472 .and_then(|p| p.last_event.as_ref())
473 .map(|e| e.event_type)
474 }
475
476 pub fn encode(&self) -> Vec<u8> {
480 let counter_data = self.counter.encode();
481 let peripheral_data = self.peripheral.as_ref().map(|p| p.encode());
482 let emergency_data = self.emergency.as_ref().map(|e| e.encode());
483 #[cfg(feature = "legacy-chat")]
484 let chat_data = self
485 .chat
486 .as_ref()
487 .filter(|c| !c.is_empty())
488 .map(|c| c.encode());
489 #[cfg(not(feature = "legacy-chat"))]
490 let chat_data: Option<Vec<u8>> = None;
491
492 let mut size = 8 + counter_data.len(); if let Some(ref pdata) = peripheral_data {
495 size += 4 + pdata.len(); }
497 if let Some(ref edata) = emergency_data {
498 size += 4 + edata.len(); }
500 if let Some(ref cdata) = chat_data {
501 size += 4 + cdata.len(); }
503
504 let mut buf = Vec::with_capacity(size);
505
506 buf.extend_from_slice(&self.version.to_le_bytes());
508 buf.extend_from_slice(&self.node_id.as_u32().to_le_bytes());
509
510 buf.extend_from_slice(&counter_data);
512
513 if let Some(pdata) = peripheral_data {
515 buf.push(EXTENDED_MARKER);
516 buf.push(0); buf.extend_from_slice(&(pdata.len() as u16).to_le_bytes());
518 buf.extend_from_slice(&pdata);
519 }
520
521 if let Some(edata) = emergency_data {
523 buf.push(EMERGENCY_MARKER);
524 buf.push(0); buf.extend_from_slice(&(edata.len() as u16).to_le_bytes());
526 buf.extend_from_slice(&edata);
527 }
528
529 if let Some(cdata) = chat_data {
531 buf.push(CHAT_MARKER);
532 buf.push(0); buf.extend_from_slice(&(cdata.len() as u16).to_le_bytes());
534 buf.extend_from_slice(&cdata);
535 }
536
537 buf
538 }
539
540 #[inline]
545 pub fn to_bytes(&self) -> Vec<u8> {
546 self.encode()
547 }
548
549 pub fn decode(data: &[u8]) -> Option<Self> {
553 if data.len() < MIN_DOCUMENT_SIZE {
554 return None;
555 }
556
557 let version = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
559 let node_id = NodeId::new(u32::from_le_bytes([data[4], data[5], data[6], data[7]]));
560
561 let counter = GCounter::decode(&data[8..])?;
563
564 let num_entries = u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
566 let mut offset = 8 + 4 + num_entries * 12;
567
568 let mut peripheral = None;
569 let mut emergency = None;
570 #[cfg(feature = "legacy-chat")]
571 let mut chat = None;
572
573 while offset < data.len() {
575 let marker = data[offset];
576
577 if marker == EXTENDED_MARKER {
578 if data.len() < offset + 4 {
580 break;
581 }
582 let _reserved = data[offset + 1];
583 let section_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
584
585 let section_start = offset + 4;
586 if data.len() < section_start + section_len {
587 break;
588 }
589
590 peripheral = Peripheral::decode(&data[section_start..section_start + section_len]);
591 offset = section_start + section_len;
592 } else if marker == EMERGENCY_MARKER {
593 if data.len() < offset + 4 {
595 break;
596 }
597 let _reserved = data[offset + 1];
598 let section_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
599
600 let section_start = offset + 4;
601 if data.len() < section_start + section_len {
602 break;
603 }
604
605 emergency =
606 EmergencyEvent::decode(&data[section_start..section_start + section_len]);
607 offset = section_start + section_len;
608 } else if marker == CHAT_MARKER {
609 if data.len() < offset + 4 {
611 break;
612 }
613 let _reserved = data[offset + 1];
614 let section_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
615
616 let section_start = offset + 4;
617 if data.len() < section_start + section_len {
618 break;
619 }
620
621 #[cfg(feature = "legacy-chat")]
622 {
623 chat = ChatCRDT::decode(&data[section_start..section_start + section_len]);
624 }
625 offset = section_start + section_len;
626 } else {
627 break;
629 }
630 }
631
632 Some(Self {
633 version,
634 node_id,
635 counter,
636 peripheral,
637 emergency,
638 #[cfg(feature = "legacy-chat")]
639 chat,
640 })
641 }
642
643 #[inline]
648 pub fn from_bytes(data: &[u8]) -> Option<Self> {
649 Self::decode(data)
650 }
651
652 pub fn total_count(&self) -> u64 {
654 self.counter.value()
655 }
656
657 pub fn encoded_size(&self) -> usize {
661 let counter_size = 4 + self.counter.node_count_total() * 12;
662 let peripheral_size = self.peripheral.as_ref().map_or(0, |p| 4 + p.encode().len());
663 let emergency_size = self.emergency.as_ref().map_or(0, |e| 4 + e.encode().len());
664 #[cfg(feature = "legacy-chat")]
665 let chat_size = self
666 .chat
667 .as_ref()
668 .filter(|c| !c.is_empty())
669 .map_or(0, |c| 4 + c.encoded_size());
670 #[cfg(not(feature = "legacy-chat"))]
671 let chat_size = 0;
672 8 + counter_size + peripheral_size + emergency_size + chat_size
673 }
674
675 pub fn exceeds_target_size(&self) -> bool {
679 self.encoded_size() > TARGET_DOCUMENT_SIZE
680 }
681
682 pub fn exceeds_max_size(&self) -> bool {
686 self.encoded_size() > MAX_DOCUMENT_SIZE
687 }
688}
689
690#[derive(Debug, Clone)]
692pub struct MergeResult {
693 pub source_node: NodeId,
695
696 pub event: Option<PeripheralEvent>,
698
699 pub peer_peripheral: Option<Peripheral>,
704
705 pub counter_changed: bool,
707
708 pub emergency_changed: bool,
710
711 pub chat_changed: bool,
713
714 pub total_count: u64,
716}
717
718impl MergeResult {
719 pub fn is_emergency(&self) -> bool {
721 self.event
722 .as_ref()
723 .is_some_and(|e| e.event_type == EventType::Emergency)
724 }
725
726 pub fn is_ack(&self) -> bool {
728 self.event
729 .as_ref()
730 .is_some_and(|e| e.event_type == EventType::Ack)
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737 use crate::sync::crdt::PeripheralType;
738
739 const TEST_TIMESTAMP: u64 = 1705276800000;
741
742 #[test]
743 fn test_document_encode_decode_minimal() {
744 let node_id = NodeId::new(0x12345678);
745 let doc = PeatDocument::new(node_id);
746
747 let encoded = doc.encode();
748 assert_eq!(encoded.len(), 12); let decoded = PeatDocument::decode(&encoded).unwrap();
751 assert_eq!(decoded.version, 1);
752 assert_eq!(decoded.node_id.as_u32(), 0x12345678);
753 assert_eq!(decoded.counter.value(), 0);
754 assert!(decoded.peripheral.is_none());
755 }
756
757 #[test]
758 fn test_document_encode_decode_with_counter() {
759 let node_id = NodeId::new(0x12345678);
760 let mut doc = PeatDocument::new(node_id);
761 doc.increment_counter();
762 doc.increment_counter();
763
764 let encoded = doc.encode();
765 assert_eq!(encoded.len(), 24);
767
768 let decoded = PeatDocument::decode(&encoded).unwrap();
769 assert_eq!(decoded.counter.value(), 2);
770 }
771
772 #[test]
773 fn test_document_encode_decode_with_peripheral() {
774 let node_id = NodeId::new(0x12345678);
775 let peripheral =
776 Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor).with_callsign("ALPHA-1");
777
778 let doc = PeatDocument::new(node_id).with_peripheral(peripheral);
779
780 let encoded = doc.encode();
781 let decoded = PeatDocument::decode(&encoded).unwrap();
782
783 assert!(decoded.peripheral.is_some());
784 let p = decoded.peripheral.unwrap();
785 assert_eq!(p.id, 0xAABBCCDD);
786 assert_eq!(p.callsign_str(), "ALPHA-1");
787 }
788
789 #[test]
790 fn test_document_encode_decode_with_event() {
791 let node_id = NodeId::new(0x12345678);
792 let mut peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor);
793 peripheral.set_event(EventType::Emergency, TEST_TIMESTAMP);
794
795 let doc = PeatDocument::new(node_id).with_peripheral(peripheral);
796
797 let encoded = doc.encode();
798 let decoded = PeatDocument::decode(&encoded).unwrap();
799
800 assert!(decoded.peripheral.is_some());
801 let p = decoded.peripheral.unwrap();
802 assert!(p.last_event.is_some());
803 let event = p.last_event.unwrap();
804 assert_eq!(event.event_type, EventType::Emergency);
805 assert_eq!(event.timestamp, TEST_TIMESTAMP);
806 }
807
808 #[test]
809 fn test_document_merge() {
810 let node1 = NodeId::new(0x11111111);
811 let node2 = NodeId::new(0x22222222);
812
813 let mut doc1 = PeatDocument::new(node1);
814 doc1.increment_counter();
815
816 let mut doc2 = PeatDocument::new(node2);
817 doc2.counter.increment(&node2, 3);
818
819 let changed = doc1.merge(&doc2);
821 assert!(changed);
822 assert_eq!(doc1.counter.value(), 4); }
824
825 #[test]
826 fn test_merge_result_helpers() {
827 let emergency_event = PeripheralEvent::new(EventType::Emergency, 123);
828 let result = MergeResult {
829 source_node: NodeId::new(0x12345678),
830 event: Some(emergency_event),
831 peer_peripheral: None,
832 counter_changed: true,
833 emergency_changed: false,
834 chat_changed: false,
835 total_count: 10,
836 };
837
838 assert!(result.is_emergency());
839 assert!(!result.is_ack());
840
841 let ack_event = PeripheralEvent::new(EventType::Ack, 456);
842 let result = MergeResult {
843 source_node: NodeId::new(0x12345678),
844 event: Some(ack_event),
845 peer_peripheral: None,
846 counter_changed: false,
847 emergency_changed: false,
848 chat_changed: false,
849 total_count: 10,
850 };
851
852 assert!(!result.is_emergency());
853 assert!(result.is_ack());
854 }
855
856 #[test]
857 fn test_document_size_calculation() {
858 use crate::sync::crdt::PeripheralType;
859
860 let node_id = NodeId::new(0x12345678);
861
862 let doc = PeatDocument::new(node_id);
864 assert_eq!(doc.encoded_size(), 12);
865 assert!(!doc.exceeds_target_size());
866
867 let mut doc = PeatDocument::new(node_id);
869 doc.increment_counter();
870 assert_eq!(doc.encoded_size(), 24);
871
872 let peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor);
874 let doc = PeatDocument::new(node_id).with_peripheral(peripheral);
875 let encoded = doc.encode();
876 assert_eq!(doc.encoded_size(), encoded.len());
877
878 let mut doc = PeatDocument::new(node_id);
880 for i in 0..10 {
881 doc.counter.increment(&NodeId::new(i), 1);
882 }
883 assert!(doc.encoded_size() < TARGET_DOCUMENT_SIZE);
884 assert!(!doc.exceeds_max_size());
885 }
886
887 #[cfg(feature = "legacy-chat")]
892 mod chat_document_tests {
893 use super::*;
894
895 #[test]
896 fn test_document_add_chat_message() {
897 let node_id = NodeId::new(0x12345678);
898 let mut doc = PeatDocument::new(node_id);
899
900 assert!(!doc.has_chat());
901 assert_eq!(doc.chat_count(), 0);
902
903 assert!(doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello mesh!"));
905 assert!(doc.has_chat());
906 assert_eq!(doc.chat_count(), 1);
907
908 assert!(!doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello mesh!"));
910 assert_eq!(doc.chat_count(), 1);
911
912 assert!(doc.add_chat_message(
914 0x12345678,
915 TEST_TIMESTAMP + 1000,
916 "ALPHA",
917 "Second message"
918 ));
919 assert_eq!(doc.chat_count(), 2);
920 }
921
922 #[test]
923 fn test_document_add_chat_reply() {
924 let node_id = NodeId::new(0x12345678);
925 let mut doc = PeatDocument::new(node_id);
926
927 doc.add_chat_message(0xAABBCCDD, TEST_TIMESTAMP, "BRAVO", "Need assistance");
929
930 assert!(doc.add_chat_reply(
932 0x12345678,
933 TEST_TIMESTAMP + 1000,
934 "ALPHA",
935 "Copy that",
936 0xAABBCCDD, TEST_TIMESTAMP ));
939
940 assert_eq!(doc.chat_count(), 2);
941
942 let chat = doc.get_chat().unwrap();
944 let reply = chat.get_message(0x12345678, TEST_TIMESTAMP + 1000).unwrap();
945 assert!(reply.is_reply());
946 assert_eq!(reply.reply_to_node, 0xAABBCCDD);
947 assert_eq!(reply.reply_to_timestamp, TEST_TIMESTAMP);
948 }
949
950 #[test]
951 fn test_document_encode_decode_with_chat() {
952 let node_id = NodeId::new(0x12345678);
953 let mut doc = PeatDocument::new(node_id);
954
955 doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "First message");
956 doc.add_chat_message(0xAABBCCDD, TEST_TIMESTAMP + 1000, "BRAVO", "Second message");
957
958 let encoded = doc.encode();
959 let decoded = PeatDocument::decode(&encoded).unwrap();
960
961 assert!(decoded.has_chat());
962 assert_eq!(decoded.chat_count(), 2);
963
964 let chat = decoded.get_chat().unwrap();
965 let msg1 = chat.get_message(0x12345678, TEST_TIMESTAMP).unwrap();
966 assert_eq!(msg1.sender(), "ALPHA");
967 assert_eq!(msg1.text(), "First message");
968
969 let msg2 = chat.get_message(0xAABBCCDD, TEST_TIMESTAMP + 1000).unwrap();
970 assert_eq!(msg2.sender(), "BRAVO");
971 assert_eq!(msg2.text(), "Second message");
972 }
973
974 #[test]
975 fn test_document_merge_with_chat() {
976 let node1 = NodeId::new(0x11111111);
977 let node2 = NodeId::new(0x22222222);
978
979 let mut doc1 = PeatDocument::new(node1);
980 doc1.add_chat_message(0x11111111, TEST_TIMESTAMP, "ALPHA", "From node 1");
981
982 let mut doc2 = PeatDocument::new(node2);
983 doc2.add_chat_message(0x22222222, TEST_TIMESTAMP + 1000, "BRAVO", "From node 2");
984
985 let changed = doc1.merge(&doc2);
987 assert!(changed);
988 assert_eq!(doc1.chat_count(), 2);
989
990 let changed = doc1.merge(&doc2);
992 assert!(!changed);
993
994 let chat = doc1.get_chat().unwrap();
996 assert!(chat.get_message(0x11111111, TEST_TIMESTAMP).is_some());
997 assert!(chat
998 .get_message(0x22222222, TEST_TIMESTAMP + 1000)
999 .is_some());
1000 }
1001
1002 #[test]
1003 fn test_document_chat_encoded_size() {
1004 let node_id = NodeId::new(0x12345678);
1005 let mut doc = PeatDocument::new(node_id);
1006
1007 let base_size = doc.encoded_size();
1008
1009 doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Test");
1011
1012 let with_chat_size = doc.encoded_size();
1014 assert!(with_chat_size > base_size);
1015
1016 let encoded = doc.encode();
1018 assert_eq!(doc.encoded_size(), encoded.len());
1019 }
1020 } }