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),
115 AuditResponse(AuditResponse),
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct FreshReplicationOffer {
128 pub key: XorName,
130 pub data: Vec<u8>,
132 pub proof_of_payment: Vec<u8>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub enum FreshReplicationResponse {
139 Accepted {
141 key: XorName,
143 },
144 Rejected {
146 key: XorName,
148 reason: String,
150 },
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct PaidNotify {
159 pub key: XorName,
161 pub proof_of_payment: Vec<u8>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct NeighborSyncRequest {
174 pub replica_hints: Vec<XorName>,
176 pub paid_hints: Vec<XorName>,
178 pub bootstrapping: bool,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct NeighborSyncResponse {
185 pub replica_hints: Vec<XorName>,
187 pub paid_hints: Vec<XorName>,
189 pub bootstrapping: bool,
191 pub rejected_keys: Vec<XorName>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct VerificationRequest {
206 pub keys: Vec<XorName>,
208 pub paid_list_check_indices: Vec<u32>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct KeyVerificationResult {
216 pub key: XorName,
218 pub present: bool,
220 pub paid: Option<bool>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct VerificationResponse {
231 pub results: Vec<KeyVerificationResult>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct FetchRequest {
242 pub key: XorName,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub enum FetchResponse {
249 Success {
251 key: XorName,
253 data: Vec<u8>,
255 },
256 NotFound {
258 key: XorName,
260 },
261 Error {
263 key: XorName,
265 reason: String,
267 },
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct AuditChallenge {
281 pub challenge_id: u64,
283 pub nonce: [u8; 32],
285 pub challenged_peer_id: [u8; 32],
287 pub keys: Vec<XorName>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub enum AuditResponse {
294 Digests {
299 challenge_id: u64,
301 digests: Vec<[u8; 32]>,
303 },
304 Bootstrapping {
306 challenge_id: u64,
308 },
309 Rejected {
314 challenge_id: u64,
316 reason: String,
318 },
319}
320
321#[must_use]
331pub fn compute_audit_digest(
332 nonce: &[u8; 32],
333 challenged_peer_id: &[u8; 32],
334 key: &XorName,
335 record_bytes: &[u8],
336) -> [u8; 32] {
337 let mut hasher = blake3::Hasher::new();
338 hasher.update(nonce);
339 hasher.update(challenged_peer_id);
340 hasher.update(key);
341 hasher.update(record_bytes);
342 *hasher.finalize().as_bytes()
343}
344
345#[derive(Debug, Clone)]
351pub enum ReplicationProtocolError {
352 SerializationFailed(String),
354 DeserializationFailed(String),
356 MessageTooLarge {
358 size: usize,
360 max_size: usize,
362 },
363}
364
365impl std::fmt::Display for ReplicationProtocolError {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 match self {
368 Self::SerializationFailed(msg) => {
369 write!(f, "replication serialization failed: {msg}")
370 }
371 Self::DeserializationFailed(msg) => {
372 write!(f, "replication deserialization failed: {msg}")
373 }
374 Self::MessageTooLarge { size, max_size } => {
375 write!(
376 f,
377 "replication message size {size} exceeds maximum {max_size}"
378 )
379 }
380 }
381 }
382}
383
384impl std::error::Error for ReplicationProtocolError {}
385
386#[cfg(test)]
391#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
392mod tests {
393 use super::*;
394
395 #[test]
398 fn fresh_replication_offer_roundtrip() {
399 let msg = ReplicationMessage {
400 request_id: 1,
401 body: ReplicationMessageBody::FreshReplicationOffer(FreshReplicationOffer {
402 key: [0xAA; 32],
403 data: vec![1, 2, 3, 4, 5],
404 proof_of_payment: vec![10, 20, 30],
405 }),
406 };
407 let encoded = msg.encode().expect("encode should succeed");
408 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
409
410 assert_eq!(decoded.request_id, 1);
411 if let ReplicationMessageBody::FreshReplicationOffer(offer) = decoded.body {
412 assert_eq!(offer.key, [0xAA; 32]);
413 assert_eq!(offer.data, vec![1, 2, 3, 4, 5]);
414 assert_eq!(offer.proof_of_payment, vec![10, 20, 30]);
415 } else {
416 panic!("expected FreshReplicationOffer");
417 }
418 }
419
420 #[test]
421 fn fresh_replication_response_accepted_roundtrip() {
422 let msg = ReplicationMessage {
423 request_id: 2,
424 body: ReplicationMessageBody::FreshReplicationResponse(
425 FreshReplicationResponse::Accepted { key: [0xBB; 32] },
426 ),
427 };
428 let encoded = msg.encode().expect("encode should succeed");
429 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
430
431 assert_eq!(decoded.request_id, 2);
432 if let ReplicationMessageBody::FreshReplicationResponse(
433 FreshReplicationResponse::Accepted { key },
434 ) = decoded.body
435 {
436 assert_eq!(key, [0xBB; 32]);
437 } else {
438 panic!("expected FreshReplicationResponse::Accepted");
439 }
440 }
441
442 #[test]
443 fn fresh_replication_response_rejected_roundtrip() {
444 let msg = ReplicationMessage {
445 request_id: 3,
446 body: ReplicationMessageBody::FreshReplicationResponse(
447 FreshReplicationResponse::Rejected {
448 key: [0xCC; 32],
449 reason: "out of range".to_string(),
450 },
451 ),
452 };
453 let encoded = msg.encode().expect("encode should succeed");
454 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
455
456 assert_eq!(decoded.request_id, 3);
457 if let ReplicationMessageBody::FreshReplicationResponse(
458 FreshReplicationResponse::Rejected { key, reason },
459 ) = decoded.body
460 {
461 assert_eq!(key, [0xCC; 32]);
462 assert_eq!(reason, "out of range");
463 } else {
464 panic!("expected FreshReplicationResponse::Rejected");
465 }
466 }
467
468 #[test]
471 fn paid_notify_roundtrip() {
472 let msg = ReplicationMessage {
473 request_id: 4,
474 body: ReplicationMessageBody::PaidNotify(PaidNotify {
475 key: [0xDD; 32],
476 proof_of_payment: vec![99, 100],
477 }),
478 };
479 let encoded = msg.encode().expect("encode should succeed");
480 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
481
482 assert_eq!(decoded.request_id, 4);
483 if let ReplicationMessageBody::PaidNotify(notify) = decoded.body {
484 assert_eq!(notify.key, [0xDD; 32]);
485 assert_eq!(notify.proof_of_payment, vec![99, 100]);
486 } else {
487 panic!("expected PaidNotify");
488 }
489 }
490
491 #[test]
494 fn neighbor_sync_request_roundtrip() {
495 let msg = ReplicationMessage {
496 request_id: 5,
497 body: ReplicationMessageBody::NeighborSyncRequest(NeighborSyncRequest {
498 replica_hints: vec![[0x01; 32], [0x02; 32]],
499 paid_hints: vec![[0x03; 32]],
500 bootstrapping: true,
501 }),
502 };
503 let encoded = msg.encode().expect("encode should succeed");
504 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
505
506 assert_eq!(decoded.request_id, 5);
507 if let ReplicationMessageBody::NeighborSyncRequest(req) = decoded.body {
508 assert_eq!(req.replica_hints.len(), 2);
509 assert_eq!(req.paid_hints.len(), 1);
510 assert!(req.bootstrapping);
511 } else {
512 panic!("expected NeighborSyncRequest");
513 }
514 }
515
516 #[test]
517 fn neighbor_sync_response_roundtrip() {
518 let msg = ReplicationMessage {
519 request_id: 6,
520 body: ReplicationMessageBody::NeighborSyncResponse(NeighborSyncResponse {
521 replica_hints: vec![[0x04; 32]],
522 paid_hints: vec![],
523 bootstrapping: false,
524 rejected_keys: vec![[0x05; 32], [0x06; 32]],
525 }),
526 };
527 let encoded = msg.encode().expect("encode should succeed");
528 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
529
530 assert_eq!(decoded.request_id, 6);
531 if let ReplicationMessageBody::NeighborSyncResponse(resp) = decoded.body {
532 assert_eq!(resp.replica_hints.len(), 1);
533 assert!(resp.paid_hints.is_empty());
534 assert!(!resp.bootstrapping);
535 assert_eq!(resp.rejected_keys.len(), 2);
536 } else {
537 panic!("expected NeighborSyncResponse");
538 }
539 }
540
541 #[test]
544 fn verification_request_roundtrip() {
545 let msg = ReplicationMessage {
546 request_id: 7,
547 body: ReplicationMessageBody::VerificationRequest(VerificationRequest {
548 keys: vec![[0x10; 32], [0x20; 32], [0x30; 32]],
549 paid_list_check_indices: vec![0, 2],
550 }),
551 };
552 let encoded = msg.encode().expect("encode should succeed");
553 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
554
555 assert_eq!(decoded.request_id, 7);
556 if let ReplicationMessageBody::VerificationRequest(req) = decoded.body {
557 assert_eq!(req.keys.len(), 3);
558 assert_eq!(req.paid_list_check_indices, vec![0, 2]);
559 } else {
560 panic!("expected VerificationRequest");
561 }
562 }
563
564 #[test]
565 fn verification_response_roundtrip() {
566 let results = vec![
567 KeyVerificationResult {
568 key: [0x10; 32],
569 present: true,
570 paid: Some(true),
571 },
572 KeyVerificationResult {
573 key: [0x20; 32],
574 present: false,
575 paid: None,
576 },
577 KeyVerificationResult {
578 key: [0x30; 32],
579 present: true,
580 paid: Some(false),
581 },
582 ];
583 let msg = ReplicationMessage {
584 request_id: 8,
585 body: ReplicationMessageBody::VerificationResponse(VerificationResponse { results }),
586 };
587 let encoded = msg.encode().expect("encode should succeed");
588 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
589
590 assert_eq!(decoded.request_id, 8);
591 if let ReplicationMessageBody::VerificationResponse(resp) = decoded.body {
592 assert_eq!(resp.results.len(), 3);
593 assert!(resp.results[0].present);
594 assert_eq!(resp.results[0].paid, Some(true));
595 assert!(!resp.results[1].present);
596 assert_eq!(resp.results[1].paid, None);
597 assert!(resp.results[2].present);
598 assert_eq!(resp.results[2].paid, Some(false));
599 } else {
600 panic!("expected VerificationResponse");
601 }
602 }
603
604 #[test]
607 fn fetch_request_roundtrip() {
608 let msg = ReplicationMessage {
609 request_id: 9,
610 body: ReplicationMessageBody::FetchRequest(FetchRequest { key: [0x40; 32] }),
611 };
612 let encoded = msg.encode().expect("encode should succeed");
613 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
614
615 assert_eq!(decoded.request_id, 9);
616 if let ReplicationMessageBody::FetchRequest(req) = decoded.body {
617 assert_eq!(req.key, [0x40; 32]);
618 } else {
619 panic!("expected FetchRequest");
620 }
621 }
622
623 #[test]
624 fn fetch_response_success_roundtrip() {
625 let msg = ReplicationMessage {
626 request_id: 10,
627 body: ReplicationMessageBody::FetchResponse(FetchResponse::Success {
628 key: [0x50; 32],
629 data: vec![7, 8, 9],
630 }),
631 };
632 let encoded = msg.encode().expect("encode should succeed");
633 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
634
635 assert_eq!(decoded.request_id, 10);
636 if let ReplicationMessageBody::FetchResponse(FetchResponse::Success { key, data }) =
637 decoded.body
638 {
639 assert_eq!(key, [0x50; 32]);
640 assert_eq!(data, vec![7, 8, 9]);
641 } else {
642 panic!("expected FetchResponse::Success");
643 }
644 }
645
646 #[test]
647 fn fetch_response_not_found_roundtrip() {
648 let msg = ReplicationMessage {
649 request_id: 11,
650 body: ReplicationMessageBody::FetchResponse(FetchResponse::NotFound {
651 key: [0x60; 32],
652 }),
653 };
654 let encoded = msg.encode().expect("encode should succeed");
655 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
656
657 assert_eq!(decoded.request_id, 11);
658 if let ReplicationMessageBody::FetchResponse(FetchResponse::NotFound { key }) = decoded.body
659 {
660 assert_eq!(key, [0x60; 32]);
661 } else {
662 panic!("expected FetchResponse::NotFound");
663 }
664 }
665
666 #[test]
667 fn fetch_response_error_roundtrip() {
668 let msg = ReplicationMessage {
669 request_id: 12,
670 body: ReplicationMessageBody::FetchResponse(FetchResponse::Error {
671 key: [0x70; 32],
672 reason: "disk full".to_string(),
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, 12);
679 if let ReplicationMessageBody::FetchResponse(FetchResponse::Error { key, reason }) =
680 decoded.body
681 {
682 assert_eq!(key, [0x70; 32]);
683 assert_eq!(reason, "disk full");
684 } else {
685 panic!("expected FetchResponse::Error");
686 }
687 }
688
689 #[test]
692 fn audit_challenge_roundtrip() {
693 let msg = ReplicationMessage {
694 request_id: 13,
695 body: ReplicationMessageBody::AuditChallenge(AuditChallenge {
696 challenge_id: 999,
697 nonce: [0xAB; 32],
698 challenged_peer_id: [0xCD; 32],
699 keys: vec![[0x01; 32], [0x02; 32]],
700 }),
701 };
702 let encoded = msg.encode().expect("encode should succeed");
703 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
704
705 assert_eq!(decoded.request_id, 13);
706 if let ReplicationMessageBody::AuditChallenge(challenge) = decoded.body {
707 assert_eq!(challenge.challenge_id, 999);
708 assert_eq!(challenge.nonce, [0xAB; 32]);
709 assert_eq!(challenge.challenged_peer_id, [0xCD; 32]);
710 assert_eq!(challenge.keys.len(), 2);
711 } else {
712 panic!("expected AuditChallenge");
713 }
714 }
715
716 #[test]
717 fn audit_response_digests_roundtrip() {
718 let digests = vec![[0x11; 32], ABSENT_KEY_DIGEST];
719 let msg = ReplicationMessage {
720 request_id: 14,
721 body: ReplicationMessageBody::AuditResponse(AuditResponse::Digests {
722 challenge_id: 999,
723 digests: digests.clone(),
724 }),
725 };
726 let encoded = msg.encode().expect("encode should succeed");
727 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
728
729 assert_eq!(decoded.request_id, 14);
730 if let ReplicationMessageBody::AuditResponse(AuditResponse::Digests {
731 challenge_id,
732 digests: decoded_digests,
733 }) = decoded.body
734 {
735 assert_eq!(challenge_id, 999);
736 assert_eq!(decoded_digests, digests);
737 } else {
738 panic!("expected AuditResponse::Digests");
739 }
740 }
741
742 #[test]
743 fn audit_response_bootstrapping_roundtrip() {
744 let msg = ReplicationMessage {
745 request_id: 15,
746 body: ReplicationMessageBody::AuditResponse(AuditResponse::Bootstrapping {
747 challenge_id: 42,
748 }),
749 };
750 let encoded = msg.encode().expect("encode should succeed");
751 let decoded = ReplicationMessage::decode(&encoded).expect("decode should succeed");
752
753 assert_eq!(decoded.request_id, 15);
754 if let ReplicationMessageBody::AuditResponse(AuditResponse::Bootstrapping {
755 challenge_id,
756 }) = decoded.body
757 {
758 assert_eq!(challenge_id, 42);
759 } else {
760 panic!("expected AuditResponse::Bootstrapping");
761 }
762 }
763
764 #[test]
767 fn decode_rejects_oversized_payload() {
768 let oversized = vec![0u8; MAX_REPLICATION_MESSAGE_SIZE + 1];
769 let result = ReplicationMessage::decode(&oversized);
770 assert!(result.is_err());
771 let err = result.unwrap_err();
772 assert!(
773 matches!(err, ReplicationProtocolError::MessageTooLarge { .. }),
774 "expected MessageTooLarge, got {err:?}"
775 );
776 }
777
778 #[test]
779 fn encode_rejects_oversized_message() {
780 let msg = ReplicationMessage {
782 request_id: 0,
783 body: ReplicationMessageBody::FreshReplicationOffer(FreshReplicationOffer {
784 key: [0; 32],
785 data: vec![0xFF; MAX_REPLICATION_MESSAGE_SIZE],
786 proof_of_payment: vec![],
787 }),
788 };
789 let result = msg.encode();
790 assert!(result.is_err());
791 let err = result.unwrap_err();
792 assert!(
793 matches!(err, ReplicationProtocolError::MessageTooLarge { .. }),
794 "expected MessageTooLarge, got {err:?}"
795 );
796 }
797
798 #[test]
801 fn decode_rejects_invalid_data() {
802 let invalid = vec![0xFF, 0xFF, 0xFF];
803 let result = ReplicationMessage::decode(&invalid);
804 assert!(result.is_err());
805 let err = result.unwrap_err();
806 assert!(
807 matches!(err, ReplicationProtocolError::DeserializationFailed(_)),
808 "expected DeserializationFailed, got {err:?}"
809 );
810 }
811
812 #[test]
815 fn audit_digest_is_deterministic() {
816 let nonce = [0x01; 32];
817 let peer_id = [0x02; 32];
818 let key: XorName = [0x03; 32];
819 let record_bytes = b"hello world";
820
821 let digest_a = compute_audit_digest(&nonce, &peer_id, &key, record_bytes);
822 let digest_b = compute_audit_digest(&nonce, &peer_id, &key, record_bytes);
823
824 assert_eq!(digest_a, digest_b, "same inputs must produce same digest");
825 }
826
827 #[test]
828 fn audit_digest_differs_with_different_nonce() {
829 let peer_id = [0x02; 32];
830 let key: XorName = [0x03; 32];
831 let record_bytes = b"hello world";
832
833 let digest_a = compute_audit_digest(&[0x01; 32], &peer_id, &key, record_bytes);
834 let digest_b = compute_audit_digest(&[0xFF; 32], &peer_id, &key, record_bytes);
835
836 assert_ne!(
837 digest_a, digest_b,
838 "different nonces must produce different digests"
839 );
840 }
841
842 #[test]
843 fn audit_digest_differs_with_different_data() {
844 let nonce = [0x01; 32];
845 let peer_id = [0x02; 32];
846 let key: XorName = [0x03; 32];
847
848 let digest_a = compute_audit_digest(&nonce, &peer_id, &key, b"data-A");
849 let digest_b = compute_audit_digest(&nonce, &peer_id, &key, b"data-B");
850
851 assert_ne!(
852 digest_a, digest_b,
853 "different data must produce different digests"
854 );
855 }
856
857 #[test]
858 fn audit_digest_differs_with_different_peer() {
859 let nonce = [0x01; 32];
860 let key: XorName = [0x03; 32];
861 let record_bytes = b"hello";
862
863 let digest_a = compute_audit_digest(&nonce, &[0x02; 32], &key, record_bytes);
864 let digest_b = compute_audit_digest(&nonce, &[0xFF; 32], &key, record_bytes);
865
866 assert_ne!(
867 digest_a, digest_b,
868 "different peer IDs must produce different digests"
869 );
870 }
871
872 #[test]
873 fn audit_digest_differs_with_different_key() {
874 let nonce = [0x01; 32];
875 let peer_id = [0x02; 32];
876 let record_bytes = b"hello";
877
878 let digest_a = compute_audit_digest(&nonce, &peer_id, &[0x03; 32], record_bytes);
879 let digest_b = compute_audit_digest(&nonce, &peer_id, &[0xFF; 32], record_bytes);
880
881 assert_ne!(
882 digest_a, digest_b,
883 "different keys must produce different digests"
884 );
885 }
886
887 #[test]
890 fn absent_key_digest_is_all_zeros() {
891 assert_eq!(ABSENT_KEY_DIGEST, [0u8; 32]);
892 }
893
894 #[test]
895 fn real_digest_differs_from_absent_sentinel() {
896 let nonce = [0x01; 32];
897 let peer_id = [0x02; 32];
898 let key: XorName = [0x03; 32];
899 let record_bytes = b"non-empty data";
900
901 let digest = compute_audit_digest(&nonce, &peer_id, &key, record_bytes);
902 assert_ne!(
903 digest, ABSENT_KEY_DIGEST,
904 "a real digest should not collide with the all-zeros sentinel"
905 );
906 }
907
908 #[test]
911 fn error_display_serialization_failed() {
912 let err = ReplicationProtocolError::SerializationFailed("boom".to_string());
913 assert_eq!(err.to_string(), "replication serialization failed: boom");
914 }
915
916 #[test]
917 fn error_display_deserialization_failed() {
918 let err = ReplicationProtocolError::DeserializationFailed("bad data".to_string());
919 assert_eq!(
920 err.to_string(),
921 "replication deserialization failed: bad data"
922 );
923 }
924
925 #[test]
926 fn error_display_message_too_large() {
927 let err = ReplicationProtocolError::MessageTooLarge {
928 size: 20_000_000,
929 max_size: MAX_REPLICATION_MESSAGE_SIZE,
930 };
931 let display = err.to_string();
932 assert!(display.contains("20000000"));
933 assert!(display.contains(&MAX_REPLICATION_MESSAGE_SIZE.to_string()));
934 }
935}