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 MIN_DOCUMENT_SIZE: usize = 8;
146
147pub const MAX_MESH_SIZE: usize = 20;
153
154pub const TARGET_DOCUMENT_SIZE: usize = 244;
158
159pub const MAX_DOCUMENT_SIZE: usize = 512;
163
164#[derive(Debug, Clone)]
170pub struct HiveDocument {
171 pub version: u32,
173
174 pub node_id: NodeId,
176
177 pub counter: GCounter,
179
180 pub peripheral: Option<Peripheral>,
182
183 pub emergency: Option<EmergencyEvent>,
185
186 #[cfg(feature = "legacy-chat")]
192 pub chat: Option<ChatCRDT>,
193}
194
195impl Default for HiveDocument {
196 fn default() -> Self {
197 Self {
198 version: 1,
199 node_id: NodeId::default(),
200 counter: GCounter::new(),
201 peripheral: None,
202 emergency: None,
203 #[cfg(feature = "legacy-chat")]
204 chat: None,
205 }
206 }
207}
208
209impl HiveDocument {
210 pub fn new(node_id: NodeId) -> Self {
212 Self {
213 version: 1,
214 node_id,
215 counter: GCounter::new(),
216 peripheral: None,
217 emergency: None,
218 #[cfg(feature = "legacy-chat")]
219 chat: None,
220 }
221 }
222
223 pub fn with_peripheral(mut self, peripheral: Peripheral) -> Self {
225 self.peripheral = Some(peripheral);
226 self
227 }
228
229 pub fn with_emergency(mut self, emergency: EmergencyEvent) -> Self {
231 self.emergency = Some(emergency);
232 self
233 }
234
235 #[cfg(feature = "legacy-chat")]
237 pub fn with_chat(mut self, chat: ChatCRDT) -> Self {
238 self.chat = Some(chat);
239 self
240 }
241
242 pub fn increment_version(&mut self) {
244 self.version = self.version.wrapping_add(1);
245 }
246
247 pub fn increment_counter(&mut self) {
249 self.counter.increment(&self.node_id, 1);
250 self.increment_version();
251 }
252
253 pub fn set_event(&mut self, event_type: EventType, timestamp: u64) {
255 if let Some(ref mut peripheral) = self.peripheral {
256 peripheral.set_event(event_type, timestamp);
257 self.increment_counter();
258 }
259 }
260
261 pub fn clear_event(&mut self) {
263 if let Some(ref mut peripheral) = self.peripheral {
264 peripheral.clear_event();
265 self.increment_version();
266 }
267 }
268
269 pub fn set_emergency(&mut self, source_node: u32, timestamp: u64, known_peers: &[u32]) {
274 self.emergency = Some(EmergencyEvent::new(source_node, timestamp, known_peers));
275 self.increment_counter();
276 }
277
278 pub fn ack_emergency(&mut self, node_id: u32) -> bool {
282 if let Some(ref mut emergency) = self.emergency {
283 if emergency.ack(node_id) {
284 self.increment_version();
285 return true;
286 }
287 }
288 false
289 }
290
291 pub fn clear_emergency(&mut self) {
293 if self.emergency.is_some() {
294 self.emergency = None;
295 self.increment_version();
296 }
297 }
298
299 pub fn get_emergency(&self) -> Option<&EmergencyEvent> {
301 self.emergency.as_ref()
302 }
303
304 pub fn has_emergency(&self) -> bool {
306 self.emergency.is_some()
307 }
308
309 #[cfg(feature = "legacy-chat")]
313 pub fn get_chat(&self) -> Option<&ChatCRDT> {
314 self.chat.as_ref()
315 }
316
317 #[cfg(feature = "legacy-chat")]
319 pub fn get_or_create_chat(&mut self) -> &mut ChatCRDT {
320 if self.chat.is_none() {
321 self.chat = Some(ChatCRDT::new());
322 }
323 self.chat.as_mut().unwrap()
324 }
325
326 #[cfg(feature = "legacy-chat")]
330 pub fn add_chat_message(
331 &mut self,
332 origin_node: u32,
333 timestamp: u64,
334 sender: &str,
335 text: &str,
336 ) -> bool {
337 use crate::sync::crdt::ChatMessage;
338
339 let mut msg = ChatMessage::new(origin_node, timestamp, sender, text);
340 msg.is_broadcast = true;
341
342 let chat = self.get_or_create_chat();
343 if chat.add_message(msg) {
344 self.increment_counter();
345 true
346 } else {
347 false
348 }
349 }
350
351 #[cfg(feature = "legacy-chat")]
353 pub fn add_chat_reply(
354 &mut self,
355 origin_node: u32,
356 timestamp: u64,
357 sender: &str,
358 text: &str,
359 reply_to_node: u32,
360 reply_to_timestamp: u64,
361 ) -> bool {
362 use crate::sync::crdt::ChatMessage;
363
364 let mut msg = ChatMessage::new(origin_node, timestamp, sender, text);
365 msg.is_broadcast = true;
366 msg.set_reply_to(reply_to_node, reply_to_timestamp);
367
368 let chat = self.get_or_create_chat();
369 if chat.add_message(msg) {
370 self.increment_counter();
371 true
372 } else {
373 false
374 }
375 }
376
377 #[cfg(feature = "legacy-chat")]
379 pub fn has_chat(&self) -> bool {
380 self.chat.as_ref().is_some_and(|c| !c.is_empty())
381 }
382
383 #[cfg(feature = "legacy-chat")]
385 pub fn chat_count(&self) -> usize {
386 self.chat.as_ref().map_or(0, |c| c.len())
387 }
388
389 pub fn merge(&mut self, other: &HiveDocument) -> bool {
393 let mut changed = false;
394
395 let old_value = self.counter.value();
397 self.counter.merge(&other.counter);
398 if self.counter.value() != old_value {
399 changed = true;
400 }
401
402 if let Some(ref other_emergency) = other.emergency {
404 match &mut self.emergency {
405 Some(ref mut our_emergency) => {
406 if our_emergency.merge(other_emergency) {
407 changed = true;
408 }
409 }
410 None => {
411 self.emergency = Some(other_emergency.clone());
412 changed = true;
413 }
414 }
415 }
416
417 #[cfg(feature = "legacy-chat")]
419 if let Some(ref other_chat) = other.chat {
420 match &mut self.chat {
421 Some(ref mut our_chat) => {
422 if our_chat.merge(other_chat) {
423 changed = true;
424 }
425 }
426 None => {
427 if !other_chat.is_empty() {
428 self.chat = Some(other_chat.clone());
429 changed = true;
430 }
431 }
432 }
433 }
434
435 if changed {
436 self.increment_version();
437 }
438 changed
439 }
440
441 pub fn current_event(&self) -> Option<EventType> {
443 self.peripheral
444 .as_ref()
445 .and_then(|p| p.last_event.as_ref())
446 .map(|e| e.event_type)
447 }
448
449 pub fn encode(&self) -> Vec<u8> {
453 let counter_data = self.counter.encode();
454 let peripheral_data = self.peripheral.as_ref().map(|p| p.encode());
455 let emergency_data = self.emergency.as_ref().map(|e| e.encode());
456 #[cfg(feature = "legacy-chat")]
457 let chat_data = self
458 .chat
459 .as_ref()
460 .filter(|c| !c.is_empty())
461 .map(|c| c.encode());
462 #[cfg(not(feature = "legacy-chat"))]
463 let chat_data: Option<Vec<u8>> = None;
464
465 let mut size = 8 + counter_data.len(); if let Some(ref pdata) = peripheral_data {
468 size += 4 + pdata.len(); }
470 if let Some(ref edata) = emergency_data {
471 size += 4 + edata.len(); }
473 if let Some(ref cdata) = chat_data {
474 size += 4 + cdata.len(); }
476
477 let mut buf = Vec::with_capacity(size);
478
479 buf.extend_from_slice(&self.version.to_le_bytes());
481 buf.extend_from_slice(&self.node_id.as_u32().to_le_bytes());
482
483 buf.extend_from_slice(&counter_data);
485
486 if let Some(pdata) = peripheral_data {
488 buf.push(EXTENDED_MARKER);
489 buf.push(0); buf.extend_from_slice(&(pdata.len() as u16).to_le_bytes());
491 buf.extend_from_slice(&pdata);
492 }
493
494 if let Some(edata) = emergency_data {
496 buf.push(EMERGENCY_MARKER);
497 buf.push(0); buf.extend_from_slice(&(edata.len() as u16).to_le_bytes());
499 buf.extend_from_slice(&edata);
500 }
501
502 if let Some(cdata) = chat_data {
504 buf.push(CHAT_MARKER);
505 buf.push(0); buf.extend_from_slice(&(cdata.len() as u16).to_le_bytes());
507 buf.extend_from_slice(&cdata);
508 }
509
510 buf
511 }
512
513 #[inline]
518 pub fn to_bytes(&self) -> Vec<u8> {
519 self.encode()
520 }
521
522 pub fn decode(data: &[u8]) -> Option<Self> {
526 if data.len() < MIN_DOCUMENT_SIZE {
527 return None;
528 }
529
530 let version = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
532 let node_id = NodeId::new(u32::from_le_bytes([data[4], data[5], data[6], data[7]]));
533
534 let counter = GCounter::decode(&data[8..])?;
536
537 let num_entries = u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
539 let mut offset = 8 + 4 + num_entries * 12;
540
541 let mut peripheral = None;
542 let mut emergency = None;
543 #[cfg(feature = "legacy-chat")]
544 let mut chat = None;
545
546 while offset < data.len() {
548 let marker = data[offset];
549
550 if marker == EXTENDED_MARKER {
551 if data.len() < offset + 4 {
553 break;
554 }
555 let _reserved = data[offset + 1];
556 let section_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
557
558 let section_start = offset + 4;
559 if data.len() < section_start + section_len {
560 break;
561 }
562
563 peripheral = Peripheral::decode(&data[section_start..section_start + section_len]);
564 offset = section_start + section_len;
565 } else if marker == EMERGENCY_MARKER {
566 if data.len() < offset + 4 {
568 break;
569 }
570 let _reserved = data[offset + 1];
571 let section_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
572
573 let section_start = offset + 4;
574 if data.len() < section_start + section_len {
575 break;
576 }
577
578 emergency =
579 EmergencyEvent::decode(&data[section_start..section_start + section_len]);
580 offset = section_start + section_len;
581 } else if marker == CHAT_MARKER {
582 if data.len() < offset + 4 {
584 break;
585 }
586 let _reserved = data[offset + 1];
587 let section_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
588
589 let section_start = offset + 4;
590 if data.len() < section_start + section_len {
591 break;
592 }
593
594 #[cfg(feature = "legacy-chat")]
595 {
596 chat = ChatCRDT::decode(&data[section_start..section_start + section_len]);
597 }
598 offset = section_start + section_len;
599 } else {
600 break;
602 }
603 }
604
605 Some(Self {
606 version,
607 node_id,
608 counter,
609 peripheral,
610 emergency,
611 #[cfg(feature = "legacy-chat")]
612 chat,
613 })
614 }
615
616 #[inline]
621 pub fn from_bytes(data: &[u8]) -> Option<Self> {
622 Self::decode(data)
623 }
624
625 pub fn total_count(&self) -> u64 {
627 self.counter.value()
628 }
629
630 pub fn encoded_size(&self) -> usize {
634 let counter_size = 4 + self.counter.node_count_total() * 12;
635 let peripheral_size = self.peripheral.as_ref().map_or(0, |p| 4 + p.encode().len());
636 let emergency_size = self.emergency.as_ref().map_or(0, |e| 4 + e.encode().len());
637 #[cfg(feature = "legacy-chat")]
638 let chat_size = self
639 .chat
640 .as_ref()
641 .filter(|c| !c.is_empty())
642 .map_or(0, |c| 4 + c.encoded_size());
643 #[cfg(not(feature = "legacy-chat"))]
644 let chat_size = 0;
645 8 + counter_size + peripheral_size + emergency_size + chat_size
646 }
647
648 pub fn exceeds_target_size(&self) -> bool {
652 self.encoded_size() > TARGET_DOCUMENT_SIZE
653 }
654
655 pub fn exceeds_max_size(&self) -> bool {
659 self.encoded_size() > MAX_DOCUMENT_SIZE
660 }
661}
662
663#[derive(Debug, Clone)]
665pub struct MergeResult {
666 pub source_node: NodeId,
668
669 pub event: Option<PeripheralEvent>,
671
672 pub peer_peripheral: Option<Peripheral>,
677
678 pub counter_changed: bool,
680
681 pub emergency_changed: bool,
683
684 pub chat_changed: bool,
686
687 pub total_count: u64,
689}
690
691impl MergeResult {
692 pub fn is_emergency(&self) -> bool {
694 self.event
695 .as_ref()
696 .is_some_and(|e| e.event_type == EventType::Emergency)
697 }
698
699 pub fn is_ack(&self) -> bool {
701 self.event
702 .as_ref()
703 .is_some_and(|e| e.event_type == EventType::Ack)
704 }
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710 use crate::sync::crdt::PeripheralType;
711
712 const TEST_TIMESTAMP: u64 = 1705276800000;
714
715 #[test]
716 fn test_document_encode_decode_minimal() {
717 let node_id = NodeId::new(0x12345678);
718 let doc = HiveDocument::new(node_id);
719
720 let encoded = doc.encode();
721 assert_eq!(encoded.len(), 12); let decoded = HiveDocument::decode(&encoded).unwrap();
724 assert_eq!(decoded.version, 1);
725 assert_eq!(decoded.node_id.as_u32(), 0x12345678);
726 assert_eq!(decoded.counter.value(), 0);
727 assert!(decoded.peripheral.is_none());
728 }
729
730 #[test]
731 fn test_document_encode_decode_with_counter() {
732 let node_id = NodeId::new(0x12345678);
733 let mut doc = HiveDocument::new(node_id);
734 doc.increment_counter();
735 doc.increment_counter();
736
737 let encoded = doc.encode();
738 assert_eq!(encoded.len(), 24);
740
741 let decoded = HiveDocument::decode(&encoded).unwrap();
742 assert_eq!(decoded.counter.value(), 2);
743 }
744
745 #[test]
746 fn test_document_encode_decode_with_peripheral() {
747 let node_id = NodeId::new(0x12345678);
748 let peripheral =
749 Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor).with_callsign("ALPHA-1");
750
751 let doc = HiveDocument::new(node_id).with_peripheral(peripheral);
752
753 let encoded = doc.encode();
754 let decoded = HiveDocument::decode(&encoded).unwrap();
755
756 assert!(decoded.peripheral.is_some());
757 let p = decoded.peripheral.unwrap();
758 assert_eq!(p.id, 0xAABBCCDD);
759 assert_eq!(p.callsign_str(), "ALPHA-1");
760 }
761
762 #[test]
763 fn test_document_encode_decode_with_event() {
764 let node_id = NodeId::new(0x12345678);
765 let mut peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor);
766 peripheral.set_event(EventType::Emergency, TEST_TIMESTAMP);
767
768 let doc = HiveDocument::new(node_id).with_peripheral(peripheral);
769
770 let encoded = doc.encode();
771 let decoded = HiveDocument::decode(&encoded).unwrap();
772
773 assert!(decoded.peripheral.is_some());
774 let p = decoded.peripheral.unwrap();
775 assert!(p.last_event.is_some());
776 let event = p.last_event.unwrap();
777 assert_eq!(event.event_type, EventType::Emergency);
778 assert_eq!(event.timestamp, TEST_TIMESTAMP);
779 }
780
781 #[test]
782 fn test_document_merge() {
783 let node1 = NodeId::new(0x11111111);
784 let node2 = NodeId::new(0x22222222);
785
786 let mut doc1 = HiveDocument::new(node1);
787 doc1.increment_counter();
788
789 let mut doc2 = HiveDocument::new(node2);
790 doc2.counter.increment(&node2, 3);
791
792 let changed = doc1.merge(&doc2);
794 assert!(changed);
795 assert_eq!(doc1.counter.value(), 4); }
797
798 #[test]
799 fn test_merge_result_helpers() {
800 let emergency_event = PeripheralEvent::new(EventType::Emergency, 123);
801 let result = MergeResult {
802 source_node: NodeId::new(0x12345678),
803 event: Some(emergency_event),
804 peer_peripheral: None,
805 counter_changed: true,
806 emergency_changed: false,
807 chat_changed: false,
808 total_count: 10,
809 };
810
811 assert!(result.is_emergency());
812 assert!(!result.is_ack());
813
814 let ack_event = PeripheralEvent::new(EventType::Ack, 456);
815 let result = MergeResult {
816 source_node: NodeId::new(0x12345678),
817 event: Some(ack_event),
818 peer_peripheral: None,
819 counter_changed: false,
820 emergency_changed: false,
821 chat_changed: false,
822 total_count: 10,
823 };
824
825 assert!(!result.is_emergency());
826 assert!(result.is_ack());
827 }
828
829 #[test]
830 fn test_document_size_calculation() {
831 use crate::sync::crdt::PeripheralType;
832
833 let node_id = NodeId::new(0x12345678);
834
835 let doc = HiveDocument::new(node_id);
837 assert_eq!(doc.encoded_size(), 12);
838 assert!(!doc.exceeds_target_size());
839
840 let mut doc = HiveDocument::new(node_id);
842 doc.increment_counter();
843 assert_eq!(doc.encoded_size(), 24);
844
845 let peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor);
847 let doc = HiveDocument::new(node_id).with_peripheral(peripheral);
848 let encoded = doc.encode();
849 assert_eq!(doc.encoded_size(), encoded.len());
850
851 let mut doc = HiveDocument::new(node_id);
853 for i in 0..10 {
854 doc.counter.increment(&NodeId::new(i), 1);
855 }
856 assert!(doc.encoded_size() < TARGET_DOCUMENT_SIZE);
857 assert!(!doc.exceeds_max_size());
858 }
859
860 #[cfg(feature = "legacy-chat")]
865 mod chat_document_tests {
866 use super::*;
867
868 #[test]
869 fn test_document_add_chat_message() {
870 let node_id = NodeId::new(0x12345678);
871 let mut doc = HiveDocument::new(node_id);
872
873 assert!(!doc.has_chat());
874 assert_eq!(doc.chat_count(), 0);
875
876 assert!(doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello mesh!"));
878 assert!(doc.has_chat());
879 assert_eq!(doc.chat_count(), 1);
880
881 assert!(!doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello mesh!"));
883 assert_eq!(doc.chat_count(), 1);
884
885 assert!(doc.add_chat_message(
887 0x12345678,
888 TEST_TIMESTAMP + 1000,
889 "ALPHA",
890 "Second message"
891 ));
892 assert_eq!(doc.chat_count(), 2);
893 }
894
895 #[test]
896 fn test_document_add_chat_reply() {
897 let node_id = NodeId::new(0x12345678);
898 let mut doc = HiveDocument::new(node_id);
899
900 doc.add_chat_message(0xAABBCCDD, TEST_TIMESTAMP, "BRAVO", "Need assistance");
902
903 assert!(doc.add_chat_reply(
905 0x12345678,
906 TEST_TIMESTAMP + 1000,
907 "ALPHA",
908 "Copy that",
909 0xAABBCCDD, TEST_TIMESTAMP ));
912
913 assert_eq!(doc.chat_count(), 2);
914
915 let chat = doc.get_chat().unwrap();
917 let reply = chat.get_message(0x12345678, TEST_TIMESTAMP + 1000).unwrap();
918 assert!(reply.is_reply());
919 assert_eq!(reply.reply_to_node, 0xAABBCCDD);
920 assert_eq!(reply.reply_to_timestamp, TEST_TIMESTAMP);
921 }
922
923 #[test]
924 fn test_document_encode_decode_with_chat() {
925 let node_id = NodeId::new(0x12345678);
926 let mut doc = HiveDocument::new(node_id);
927
928 doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "First message");
929 doc.add_chat_message(0xAABBCCDD, TEST_TIMESTAMP + 1000, "BRAVO", "Second message");
930
931 let encoded = doc.encode();
932 let decoded = HiveDocument::decode(&encoded).unwrap();
933
934 assert!(decoded.has_chat());
935 assert_eq!(decoded.chat_count(), 2);
936
937 let chat = decoded.get_chat().unwrap();
938 let msg1 = chat.get_message(0x12345678, TEST_TIMESTAMP).unwrap();
939 assert_eq!(msg1.sender(), "ALPHA");
940 assert_eq!(msg1.text(), "First message");
941
942 let msg2 = chat.get_message(0xAABBCCDD, TEST_TIMESTAMP + 1000).unwrap();
943 assert_eq!(msg2.sender(), "BRAVO");
944 assert_eq!(msg2.text(), "Second message");
945 }
946
947 #[test]
948 fn test_document_merge_with_chat() {
949 let node1 = NodeId::new(0x11111111);
950 let node2 = NodeId::new(0x22222222);
951
952 let mut doc1 = HiveDocument::new(node1);
953 doc1.add_chat_message(0x11111111, TEST_TIMESTAMP, "ALPHA", "From node 1");
954
955 let mut doc2 = HiveDocument::new(node2);
956 doc2.add_chat_message(0x22222222, TEST_TIMESTAMP + 1000, "BRAVO", "From node 2");
957
958 let changed = doc1.merge(&doc2);
960 assert!(changed);
961 assert_eq!(doc1.chat_count(), 2);
962
963 let changed = doc1.merge(&doc2);
965 assert!(!changed);
966
967 let chat = doc1.get_chat().unwrap();
969 assert!(chat.get_message(0x11111111, TEST_TIMESTAMP).is_some());
970 assert!(chat
971 .get_message(0x22222222, TEST_TIMESTAMP + 1000)
972 .is_some());
973 }
974
975 #[test]
976 fn test_document_chat_encoded_size() {
977 let node_id = NodeId::new(0x12345678);
978 let mut doc = HiveDocument::new(node_id);
979
980 let base_size = doc.encoded_size();
981
982 doc.add_chat_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Test");
984
985 let with_chat_size = doc.encoded_size();
987 assert!(with_chat_size > base_size);
988
989 let encoded = doc.encode();
991 assert_eq!(doc.encoded_size(), encoded.len());
992 }
993 } }