1use serde::{Deserialize, Serialize};
7
8use crate::ant_protocol::XorName;
9
10pub use super::config::MAX_REPLICATION_MESSAGE_SIZE;
11
12pub const ABSENT_KEY_DIGEST: [u8; 32] = [0u8; 32];
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ReplicationMessage {
28 pub request_id: u64,
30 pub body: ReplicationMessageBody,
32}
33
34impl ReplicationMessage {
35 pub fn encode(&self) -> Result<Vec<u8>, ReplicationProtocolError> {
42 let bytes = postcard::to_stdvec(self)
43 .map_err(|e| ReplicationProtocolError::SerializationFailed(e.to_string()))?;
44
45 if bytes.len() > MAX_REPLICATION_MESSAGE_SIZE {
46 return Err(ReplicationProtocolError::MessageTooLarge {
47 size: bytes.len(),
48 max_size: MAX_REPLICATION_MESSAGE_SIZE,
49 });
50 }
51
52 Ok(bytes)
53 }
54
55 pub fn decode(data: &[u8]) -> Result<Self, ReplicationProtocolError> {
67 if data.len() > MAX_REPLICATION_MESSAGE_SIZE {
68 return Err(ReplicationProtocolError::MessageTooLarge {
69 size: data.len(),
70 max_size: MAX_REPLICATION_MESSAGE_SIZE,
71 });
72 }
73 postcard::from_bytes(data)
74 .map_err(|e| ReplicationProtocolError::DeserializationFailed(e.to_string()))
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
84pub enum ReplicationMessageBody {
85 FreshReplicationOffer(FreshReplicationOffer),
88 FreshReplicationResponse(FreshReplicationResponse),
90
91 PaidNotify(PaidNotify),
93
94 NeighborSyncRequest(NeighborSyncRequest),
97 NeighborSyncResponse(NeighborSyncResponse),
99
100 VerificationRequest(VerificationRequest),
103 VerificationResponse(VerificationResponse),
105
106 FetchRequest(FetchRequest),
109 FetchResponse(FetchResponse),
111
112 AuditChallenge(AuditChallenge),
116 AuditResponse(AuditResponse),
118
119 SubtreeAuditChallenge(SubtreeAuditChallenge),
122 SubtreeAuditResponse(SubtreeAuditResponse),
124 SubtreeByteChallenge(SubtreeByteChallenge),
126 SubtreeByteResponse(SubtreeByteResponse),
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct FreshReplicationOffer {
139 pub key: XorName,
141 pub data: Vec<u8>,
143 pub proof_of_payment: Vec<u8>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub enum FreshReplicationResponse {
150 Accepted {
152 key: XorName,
154 },
155 Rejected {
157 key: XorName,
159 reason: String,
161 },
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct PaidNotify {
170 pub key: XorName,
172 pub proof_of_payment: Vec<u8>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct NeighborSyncRequest {
185 pub replica_hints: Vec<XorName>,
187 pub paid_hints: Vec<XorName>,
189 pub bootstrapping: bool,
191 #[serde(default)]
198 pub commitment: Option<crate::replication::commitment::StorageCommitment>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct NeighborSyncResponse {
204 pub replica_hints: Vec<XorName>,
206 pub paid_hints: Vec<XorName>,
208 pub bootstrapping: bool,
210 pub rejected_keys: Vec<XorName>,
212 #[serde(default)]
215 pub commitment: Option<crate::replication::commitment::StorageCommitment>,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct VerificationRequest {
229 pub keys: Vec<XorName>,
231 pub paid_list_check_indices: Vec<u32>,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct KeyVerificationResult {
239 pub key: XorName,
241 pub present: bool,
243 pub paid: Option<bool>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct VerificationResponse {
254 pub results: Vec<KeyVerificationResult>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct FetchRequest {
265 pub key: XorName,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub enum FetchResponse {
272 Success {
274 key: XorName,
276 data: Vec<u8>,
278 },
279 NotFound {
281 key: XorName,
283 },
284 Error {
286 key: XorName,
288 reason: String,
290 },
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct AuditChallenge {
307 pub challenge_id: u64,
309 pub nonce: [u8; 32],
311 pub challenged_peer_id: [u8; 32],
313 pub keys: Vec<XorName>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
319pub enum AuditResponse {
320 Digests {
325 challenge_id: u64,
327 digests: Vec<[u8; 32]>,
329 },
330 Bootstrapping {
332 challenge_id: u64,
334 },
335 Rejected {
340 challenge_id: u64,
342 reason: String,
344 },
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct SubtreeAuditChallenge {
360 pub challenge_id: u64,
362 pub nonce: [u8; 32],
365 pub challenged_peer_id: [u8; 32],
367 pub expected_commitment_hash: [u8; 32],
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub enum SubtreeAuditResponse {
376 Proof {
395 challenge_id: u64,
397 commitment: crate::replication::commitment::StorageCommitment,
399 proof: crate::replication::subtree::SubtreeProof,
401 },
402 Bootstrapping {
404 challenge_id: u64,
406 },
407 Rejected {
410 challenge_id: u64,
412 kind: RejectKind,
414 reason: String,
416 },
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
447pub enum RejectKind {
448 UnknownCommitment,
451 Transient,
455 Protocol,
458}
459
460impl RejectKind {
461 #[must_use]
466 pub fn is_graced(self) -> bool {
467 matches!(self, Self::UnknownCommitment | Self::Transient)
468 }
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct SubtreeByteChallenge {
489 pub challenge_id: u64,
492 pub nonce: [u8; 32],
495 pub challenged_peer_id: [u8; 32],
497 pub expected_commitment_hash: [u8; 32],
500 pub keys: Vec<XorName>,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
508pub enum SubtreeByteItem {
509 Present {
511 key: XorName,
513 bytes: Vec<u8>,
515 },
516 Absent {
522 key: XorName,
524 },
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
536pub enum SubtreeByteResponse {
537 Items {
539 challenge_id: u64,
541 items: Vec<SubtreeByteItem>,
543 },
544 Bootstrapping {
546 challenge_id: u64,
548 },
549 Rejected {
553 challenge_id: u64,
555 kind: RejectKind,
557 reason: String,
559 },
560}
561
562#[must_use]
572pub fn compute_audit_digest(
573 nonce: &[u8; 32],
574 challenged_peer_id: &[u8; 32],
575 key: &XorName,
576 record_bytes: &[u8],
577) -> [u8; 32] {
578 let mut hasher = blake3::Hasher::new();
579 hasher.update(nonce);
580 hasher.update(challenged_peer_id);
581 hasher.update(key);
582 hasher.update(record_bytes);
583 *hasher.finalize().as_bytes()
584}
585
586#[derive(Debug, Clone)]
592pub enum ReplicationProtocolError {
593 SerializationFailed(String),
595 DeserializationFailed(String),
597 MessageTooLarge {
599 size: usize,
601 max_size: usize,
603 },
604}
605
606impl std::fmt::Display for ReplicationProtocolError {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 match self {
609 Self::SerializationFailed(msg) => {
610 write!(f, "replication serialization failed: {msg}")
611 }
612 Self::DeserializationFailed(msg) => {
613 write!(f, "replication deserialization failed: {msg}")
614 }
615 Self::MessageTooLarge { size, max_size } => {
616 write!(
617 f,
618 "replication message size {size} exceeds maximum {max_size}"
619 )
620 }
621 }
622 }
623}
624
625impl std::error::Error for ReplicationProtocolError {}
626
627#[cfg(test)]
632#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
633mod tests {
634 use super::*;
635
636 #[test]
639 fn max_batch_worst_case_byte_response_fits_wire_cap() {
640 let items: Vec<SubtreeByteItem> = (0..crate::replication::config::MAX_BYTE_CHALLENGE_KEYS)
645 .map(|i| SubtreeByteItem::Present {
646 key: [u8::try_from(i).unwrap_or(u8::MAX); 32],
647 bytes: vec![0xAB; crate::ant_protocol::MAX_CHUNK_SIZE],
648 })
649 .collect();
650 let msg = ReplicationMessage {
651 request_id: 7,
652 body: ReplicationMessageBody::SubtreeByteResponse(SubtreeByteResponse::Items {
653 challenge_id: 7,
654 items,
655 }),
656 };
657 let encoded = msg
658 .encode()
659 .expect("worst-case max-batch byte response must fit the wire cap");
660 assert!(encoded.len() <= MAX_REPLICATION_MESSAGE_SIZE);
661 }
662
663 #[test]
666 fn fresh_replication_offer_roundtrip() {
667 let msg = ReplicationMessage {
668 request_id: 1,
669 body: ReplicationMessageBody::FreshReplicationOffer(FreshReplicationOffer {
670 key: [0xAA; 32],
671 data: vec![1, 2, 3, 4, 5],
672 proof_of_payment: vec![10, 20, 30],
673 }),
674 };
675 let encoded = msg.encode().expect("encode should succeed");
676 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
677
678 assert_eq!(decoded.request_id, 1);
679 if let ReplicationMessageBody::FreshReplicationOffer(offer) = decoded.body {
680 assert_eq!(offer.key, [0xAA; 32]);
681 assert_eq!(offer.data, vec![1, 2, 3, 4, 5]);
682 assert_eq!(offer.proof_of_payment, vec![10, 20, 30]);
683 } else {
684 panic!("expected FreshReplicationOffer");
685 }
686 }
687
688 #[test]
689 fn fresh_replication_response_accepted_roundtrip() {
690 let msg = ReplicationMessage {
691 request_id: 2,
692 body: ReplicationMessageBody::FreshReplicationResponse(
693 FreshReplicationResponse::Accepted { key: [0xBB; 32] },
694 ),
695 };
696 let encoded = msg.encode().expect("encode should succeed");
697 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
698
699 assert_eq!(decoded.request_id, 2);
700 if let ReplicationMessageBody::FreshReplicationResponse(
701 FreshReplicationResponse::Accepted { key },
702 ) = decoded.body
703 {
704 assert_eq!(key, [0xBB; 32]);
705 } else {
706 panic!("expected FreshReplicationResponse::Accepted");
707 }
708 }
709
710 #[test]
711 fn fresh_replication_response_rejected_roundtrip() {
712 let msg = ReplicationMessage {
713 request_id: 3,
714 body: ReplicationMessageBody::FreshReplicationResponse(
715 FreshReplicationResponse::Rejected {
716 key: [0xCC; 32],
717 reason: "out of range".to_string(),
718 },
719 ),
720 };
721 let encoded = msg.encode().expect("encode should succeed");
722 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
723
724 assert_eq!(decoded.request_id, 3);
725 if let ReplicationMessageBody::FreshReplicationResponse(
726 FreshReplicationResponse::Rejected { key, reason },
727 ) = decoded.body
728 {
729 assert_eq!(key, [0xCC; 32]);
730 assert_eq!(reason, "out of range");
731 } else {
732 panic!("expected FreshReplicationResponse::Rejected");
733 }
734 }
735
736 #[test]
739 fn paid_notify_roundtrip() {
740 let msg = ReplicationMessage {
741 request_id: 4,
742 body: ReplicationMessageBody::PaidNotify(PaidNotify {
743 key: [0xDD; 32],
744 proof_of_payment: vec![99, 100],
745 }),
746 };
747 let encoded = msg.encode().expect("encode should succeed");
748 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
749
750 assert_eq!(decoded.request_id, 4);
751 if let ReplicationMessageBody::PaidNotify(notify) = decoded.body {
752 assert_eq!(notify.key, [0xDD; 32]);
753 assert_eq!(notify.proof_of_payment, vec![99, 100]);
754 } else {
755 panic!("expected PaidNotify");
756 }
757 }
758
759 #[test]
775 fn old_decoder_tolerates_new_neighbor_sync_request() {
776 use serde::Deserialize;
777 #[derive(Deserialize)]
778 struct OldNeighborSyncRequest {
779 #[allow(dead_code)]
780 pub replica_hints: Vec<XorName>,
781 #[allow(dead_code)]
782 pub paid_hints: Vec<XorName>,
783 #[allow(dead_code)]
784 pub bootstrapping: bool,
785 }
786
787 let new_req = NeighborSyncRequest {
788 replica_hints: vec![[0x01; 32], [0x02; 32]],
789 paid_hints: vec![[0x03; 32]],
790 bootstrapping: true,
791 commitment: None,
792 };
793 let encoded = postcard::to_stdvec(&new_req).expect("encode");
794 let old_decoded: OldNeighborSyncRequest =
795 postcard::from_bytes(&encoded).expect("old decoder accepts");
796 assert_eq!(old_decoded.replica_hints.len(), 2);
799 assert_eq!(old_decoded.paid_hints.len(), 1);
800 assert!(old_decoded.bootstrapping);
801 }
802
803 #[test]
805 fn old_decoder_tolerates_new_neighbor_sync_response() {
806 use serde::Deserialize;
807 #[derive(Deserialize)]
808 struct OldNeighborSyncResponse {
809 #[allow(dead_code)]
810 pub replica_hints: Vec<XorName>,
811 #[allow(dead_code)]
812 pub paid_hints: Vec<XorName>,
813 #[allow(dead_code)]
814 pub bootstrapping: bool,
815 #[allow(dead_code)]
816 pub rejected_keys: Vec<XorName>,
817 }
818
819 let new_resp = NeighborSyncResponse {
820 replica_hints: vec![[0x04; 32]],
821 paid_hints: vec![],
822 bootstrapping: false,
823 rejected_keys: vec![[0x05; 32]],
824 commitment: None,
825 };
826 let encoded = postcard::to_stdvec(&new_resp).expect("encode");
827 let old_decoded: OldNeighborSyncResponse =
828 postcard::from_bytes(&encoded).expect("old decoder accepts");
829 assert_eq!(old_decoded.replica_hints.len(), 1);
830 assert_eq!(old_decoded.rejected_keys.len(), 1);
831 }
832
833 #[test]
837 fn new_peer_roundtrips_with_commitment_some() {
838 use crate::replication::commitment::{sign_commitment, StorageCommitment};
839 use saorsa_pqc::api::sig::ml_dsa_65;
840
841 let (pk, sk) = ml_dsa_65().generate_keypair().expect("keygen");
842 let root = [0x7Fu8; 32];
843 let sender = [0xCCu8; 32];
844 let pk_bytes = pk.to_bytes();
845 let sig = sign_commitment(&sk, &root, 3, &sender, &pk_bytes).expect("sign");
846 let commitment = StorageCommitment {
847 root,
848 key_count: 3,
849 sender_peer_id: sender,
850 sender_public_key: pk_bytes,
851 signature: sig,
852 };
853
854 let req = NeighborSyncRequest {
855 replica_hints: vec![[0x01; 32]],
856 paid_hints: vec![],
857 bootstrapping: false,
858 commitment: Some(commitment.clone()),
859 };
860 let encoded = postcard::to_stdvec(&req).expect("encode");
861 let decoded: NeighborSyncRequest = postcard::from_bytes(&encoded).expect("new decoder");
862 assert_eq!(decoded.commitment, Some(commitment));
863 }
864
865 #[test]
866 fn neighbor_sync_request_roundtrip() {
867 let msg = ReplicationMessage {
868 request_id: 5,
869 body: ReplicationMessageBody::NeighborSyncRequest(NeighborSyncRequest {
870 replica_hints: vec![[0x01; 32], [0x02; 32]],
871 paid_hints: vec![[0x03; 32]],
872 bootstrapping: true,
873 commitment: None,
874 }),
875 };
876 let encoded = msg.encode().expect("encode should succeed");
877 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
878
879 assert_eq!(decoded.request_id, 5);
880 if let ReplicationMessageBody::NeighborSyncRequest(req) = decoded.body {
881 assert_eq!(req.replica_hints.len(), 2);
882 assert_eq!(req.paid_hints.len(), 1);
883 assert!(req.bootstrapping);
884 } else {
885 panic!("expected NeighborSyncRequest");
886 }
887 }
888
889 #[test]
890 fn neighbor_sync_response_roundtrip() {
891 let msg = ReplicationMessage {
892 request_id: 6,
893 body: ReplicationMessageBody::NeighborSyncResponse(NeighborSyncResponse {
894 replica_hints: vec![[0x04; 32]],
895 paid_hints: vec![],
896 bootstrapping: false,
897 rejected_keys: vec![[0x05; 32], [0x06; 32]],
898 commitment: None,
899 }),
900 };
901 let encoded = msg.encode().expect("encode should succeed");
902 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
903
904 assert_eq!(decoded.request_id, 6);
905 if let ReplicationMessageBody::NeighborSyncResponse(resp) = decoded.body {
906 assert_eq!(resp.replica_hints.len(), 1);
907 assert!(resp.paid_hints.is_empty());
908 assert!(!resp.bootstrapping);
909 assert_eq!(resp.rejected_keys.len(), 2);
910 } else {
911 panic!("expected NeighborSyncResponse");
912 }
913 }
914
915 #[test]
918 fn verification_request_roundtrip() {
919 let msg = ReplicationMessage {
920 request_id: 7,
921 body: ReplicationMessageBody::VerificationRequest(VerificationRequest {
922 keys: vec![[0x10; 32], [0x20; 32], [0x30; 32]],
923 paid_list_check_indices: vec![0, 2],
924 }),
925 };
926 let encoded = msg.encode().expect("encode should succeed");
927 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
928
929 assert_eq!(decoded.request_id, 7);
930 if let ReplicationMessageBody::VerificationRequest(req) = decoded.body {
931 assert_eq!(req.keys.len(), 3);
932 assert_eq!(req.paid_list_check_indices, vec![0, 2]);
933 } else {
934 panic!("expected VerificationRequest");
935 }
936 }
937
938 #[test]
939 fn verification_response_roundtrip() {
940 let results = vec![
941 KeyVerificationResult {
942 key: [0x10; 32],
943 present: true,
944 paid: Some(true),
945 },
946 KeyVerificationResult {
947 key: [0x20; 32],
948 present: false,
949 paid: None,
950 },
951 KeyVerificationResult {
952 key: [0x30; 32],
953 present: true,
954 paid: Some(false),
955 },
956 ];
957 let msg = ReplicationMessage {
958 request_id: 8,
959 body: ReplicationMessageBody::VerificationResponse(VerificationResponse { results }),
960 };
961 let encoded = msg.encode().expect("encode should succeed");
962 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
963
964 assert_eq!(decoded.request_id, 8);
965 if let ReplicationMessageBody::VerificationResponse(resp) = decoded.body {
966 assert_eq!(resp.results.len(), 3);
967 assert!(resp.results[0].present);
968 assert_eq!(resp.results[0].paid, Some(true));
969 assert!(!resp.results[1].present);
970 assert_eq!(resp.results[1].paid, None);
971 assert!(resp.results[2].present);
972 assert_eq!(resp.results[2].paid, Some(false));
973 } else {
974 panic!("expected VerificationResponse");
975 }
976 }
977
978 #[test]
981 fn fetch_request_roundtrip() {
982 let msg = ReplicationMessage {
983 request_id: 9,
984 body: ReplicationMessageBody::FetchRequest(FetchRequest { key: [0x40; 32] }),
985 };
986 let encoded = msg.encode().expect("encode should succeed");
987 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
988
989 assert_eq!(decoded.request_id, 9);
990 if let ReplicationMessageBody::FetchRequest(req) = decoded.body {
991 assert_eq!(req.key, [0x40; 32]);
992 } else {
993 panic!("expected FetchRequest");
994 }
995 }
996
997 #[test]
998 fn fetch_response_success_roundtrip() {
999 let msg = ReplicationMessage {
1000 request_id: 10,
1001 body: ReplicationMessageBody::FetchResponse(FetchResponse::Success {
1002 key: [0x50; 32],
1003 data: vec![7, 8, 9],
1004 }),
1005 };
1006 let encoded = msg.encode().expect("encode should succeed");
1007 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
1008
1009 assert_eq!(decoded.request_id, 10);
1010 if let ReplicationMessageBody::FetchResponse(FetchResponse::Success { key, data }) =
1011 decoded.body
1012 {
1013 assert_eq!(key, [0x50; 32]);
1014 assert_eq!(data, vec![7, 8, 9]);
1015 } else {
1016 panic!("expected FetchResponse::Success");
1017 }
1018 }
1019
1020 #[test]
1021 fn fetch_response_not_found_roundtrip() {
1022 let msg = ReplicationMessage {
1023 request_id: 11,
1024 body: ReplicationMessageBody::FetchResponse(FetchResponse::NotFound {
1025 key: [0x60; 32],
1026 }),
1027 };
1028 let encoded = msg.encode().expect("encode should succeed");
1029 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
1030
1031 assert_eq!(decoded.request_id, 11);
1032 if let ReplicationMessageBody::FetchResponse(FetchResponse::NotFound { key }) = decoded.body
1033 {
1034 assert_eq!(key, [0x60; 32]);
1035 } else {
1036 panic!("expected FetchResponse::NotFound");
1037 }
1038 }
1039
1040 #[test]
1041 fn fetch_response_error_roundtrip() {
1042 let msg = ReplicationMessage {
1043 request_id: 12,
1044 body: ReplicationMessageBody::FetchResponse(FetchResponse::Error {
1045 key: [0x70; 32],
1046 reason: "disk full".to_string(),
1047 }),
1048 };
1049 let encoded = msg.encode().expect("encode should succeed");
1050 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
1051
1052 assert_eq!(decoded.request_id, 12);
1053 if let ReplicationMessageBody::FetchResponse(FetchResponse::Error { key, reason }) =
1054 decoded.body
1055 {
1056 assert_eq!(key, [0x70; 32]);
1057 assert_eq!(reason, "disk full");
1058 } else {
1059 panic!("expected FetchResponse::Error");
1060 }
1061 }
1062
1063 #[test]
1066 fn audit_challenge_roundtrip() {
1067 let msg = ReplicationMessage {
1068 request_id: 13,
1069 body: ReplicationMessageBody::AuditChallenge(AuditChallenge {
1070 challenge_id: 999,
1071 nonce: [0xAB; 32],
1072 challenged_peer_id: [0xCD; 32],
1073 keys: vec![[0x01; 32], [0x02; 32]],
1074 }),
1075 };
1076 let encoded = msg.encode().expect("encode should succeed");
1077 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
1078
1079 assert_eq!(decoded.request_id, 13);
1080 if let ReplicationMessageBody::AuditChallenge(challenge) = decoded.body {
1081 assert_eq!(challenge.challenge_id, 999);
1082 assert_eq!(challenge.nonce, [0xAB; 32]);
1083 assert_eq!(challenge.challenged_peer_id, [0xCD; 32]);
1084 assert_eq!(challenge.keys.len(), 2);
1085 } else {
1086 panic!("expected AuditChallenge");
1087 }
1088 }
1089
1090 #[test]
1091 fn audit_response_digests_roundtrip() {
1092 let digests = vec![[0x11; 32], ABSENT_KEY_DIGEST];
1093 let msg = ReplicationMessage {
1094 request_id: 14,
1095 body: ReplicationMessageBody::AuditResponse(AuditResponse::Digests {
1096 challenge_id: 999,
1097 digests: digests.clone(),
1098 }),
1099 };
1100 let encoded = msg.encode().expect("encode should succeed");
1101 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
1102
1103 assert_eq!(decoded.request_id, 14);
1104 if let ReplicationMessageBody::AuditResponse(AuditResponse::Digests {
1105 challenge_id,
1106 digests: decoded_digests,
1107 }) = decoded.body
1108 {
1109 assert_eq!(challenge_id, 999);
1110 assert_eq!(decoded_digests, digests);
1111 } else {
1112 panic!("expected AuditResponse::Digests");
1113 }
1114 }
1115
1116 #[test]
1117 fn audit_response_bootstrapping_roundtrip() {
1118 let msg = ReplicationMessage {
1119 request_id: 15,
1120 body: ReplicationMessageBody::AuditResponse(AuditResponse::Bootstrapping {
1121 challenge_id: 42,
1122 }),
1123 };
1124 let encoded = msg.encode().expect("encode should succeed");
1125 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
1126
1127 assert_eq!(decoded.request_id, 15);
1128 if let ReplicationMessageBody::AuditResponse(AuditResponse::Bootstrapping {
1129 challenge_id,
1130 }) = decoded.body
1131 {
1132 assert_eq!(challenge_id, 42);
1133 } else {
1134 panic!("expected AuditResponse::Bootstrapping");
1135 }
1136 }
1137
1138 #[test]
1141 fn decode_rejects_oversized_payload() {
1142 let oversized = vec![0u8; MAX_REPLICATION_MESSAGE_SIZE + 1];
1143 let result = ReplicationMessage::decode(&oversized);
1144 assert!(result.is_err());
1145 let err = result.unwrap_err();
1146 assert!(
1147 matches!(err, ReplicationProtocolError::MessageTooLarge { .. }),
1148 "expected MessageTooLarge, got {err:?}"
1149 );
1150 }
1151
1152 #[test]
1153 fn encode_rejects_oversized_message() {
1154 let msg = ReplicationMessage {
1156 request_id: 0,
1157 body: ReplicationMessageBody::FreshReplicationOffer(FreshReplicationOffer {
1158 key: [0; 32],
1159 data: vec![0xFF; MAX_REPLICATION_MESSAGE_SIZE],
1160 proof_of_payment: vec![],
1161 }),
1162 };
1163 let result = msg.encode();
1164 assert!(result.is_err());
1165 let err = result.unwrap_err();
1166 assert!(
1167 matches!(err, ReplicationProtocolError::MessageTooLarge { .. }),
1168 "expected MessageTooLarge, got {err:?}"
1169 );
1170 }
1171
1172 #[test]
1175 fn decode_rejects_invalid_data() {
1176 let invalid = vec![0xFF, 0xFF, 0xFF];
1177 let result = ReplicationMessage::decode(&invalid);
1178 assert!(result.is_err());
1179 let err = result.unwrap_err();
1180 assert!(
1181 matches!(err, ReplicationProtocolError::DeserializationFailed(_)),
1182 "expected DeserializationFailed, got {err:?}"
1183 );
1184 }
1185
1186 #[test]
1189 fn audit_digest_is_deterministic() {
1190 let nonce = [0x01; 32];
1191 let peer_id = [0x02; 32];
1192 let key: XorName = [0x03; 32];
1193 let record_bytes = b"hello world";
1194
1195 let digest_a = compute_audit_digest(&nonce, &peer_id, &key, record_bytes);
1196 let digest_b = compute_audit_digest(&nonce, &peer_id, &key, record_bytes);
1197
1198 assert_eq!(digest_a, digest_b, "same inputs must produce same digest");
1199 }
1200
1201 #[test]
1202 fn audit_digest_differs_with_different_nonce() {
1203 let peer_id = [0x02; 32];
1204 let key: XorName = [0x03; 32];
1205 let record_bytes = b"hello world";
1206
1207 let digest_a = compute_audit_digest(&[0x01; 32], &peer_id, &key, record_bytes);
1208 let digest_b = compute_audit_digest(&[0xFF; 32], &peer_id, &key, record_bytes);
1209
1210 assert_ne!(
1211 digest_a, digest_b,
1212 "different nonces must produce different digests"
1213 );
1214 }
1215
1216 #[test]
1217 fn audit_digest_differs_with_different_data() {
1218 let nonce = [0x01; 32];
1219 let peer_id = [0x02; 32];
1220 let key: XorName = [0x03; 32];
1221
1222 let digest_a = compute_audit_digest(&nonce, &peer_id, &key, b"data-A");
1223 let digest_b = compute_audit_digest(&nonce, &peer_id, &key, b"data-B");
1224
1225 assert_ne!(
1226 digest_a, digest_b,
1227 "different data must produce different digests"
1228 );
1229 }
1230
1231 #[test]
1232 fn audit_digest_differs_with_different_peer() {
1233 let nonce = [0x01; 32];
1234 let key: XorName = [0x03; 32];
1235 let record_bytes = b"hello";
1236
1237 let digest_a = compute_audit_digest(&nonce, &[0x02; 32], &key, record_bytes);
1238 let digest_b = compute_audit_digest(&nonce, &[0xFF; 32], &key, record_bytes);
1239
1240 assert_ne!(
1241 digest_a, digest_b,
1242 "different peer IDs must produce different digests"
1243 );
1244 }
1245
1246 #[test]
1247 fn audit_digest_differs_with_different_key() {
1248 let nonce = [0x01; 32];
1249 let peer_id = [0x02; 32];
1250 let record_bytes = b"hello";
1251
1252 let digest_a = compute_audit_digest(&nonce, &peer_id, &[0x03; 32], record_bytes);
1253 let digest_b = compute_audit_digest(&nonce, &peer_id, &[0xFF; 32], record_bytes);
1254
1255 assert_ne!(
1256 digest_a, digest_b,
1257 "different keys must produce different digests"
1258 );
1259 }
1260
1261 #[test]
1264 fn absent_key_digest_is_all_zeros() {
1265 assert_eq!(ABSENT_KEY_DIGEST, [0u8; 32]);
1266 }
1267
1268 #[test]
1269 fn real_digest_differs_from_absent_sentinel() {
1270 let nonce = [0x01; 32];
1271 let peer_id = [0x02; 32];
1272 let key: XorName = [0x03; 32];
1273 let record_bytes = b"non-empty data";
1274
1275 let digest = compute_audit_digest(&nonce, &peer_id, &key, record_bytes);
1276 assert_ne!(
1277 digest, ABSENT_KEY_DIGEST,
1278 "a real digest should not collide with the all-zeros sentinel"
1279 );
1280 }
1281
1282 #[test]
1285 fn error_display_serialization_failed() {
1286 let err = ReplicationProtocolError::SerializationFailed("boom".to_string());
1287 assert_eq!(err.to_string(), "replication serialization failed: boom");
1288 }
1289
1290 #[test]
1291 fn error_display_deserialization_failed() {
1292 let err = ReplicationProtocolError::DeserializationFailed("bad data".to_string());
1293 assert_eq!(
1294 err.to_string(),
1295 "replication deserialization failed: bad data"
1296 );
1297 }
1298
1299 #[test]
1300 fn error_display_message_too_large() {
1301 let err = ReplicationProtocolError::MessageTooLarge {
1302 size: 20_000_000,
1303 max_size: MAX_REPLICATION_MESSAGE_SIZE,
1304 };
1305 let display = err.to_string();
1306 assert!(display.contains("20000000"));
1307 assert!(display.contains(&MAX_REPLICATION_MESSAGE_SIZE.to_string()));
1308 }
1309}