1#[cfg(feature = "schema")]
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use super::core::{
8 Bytes, ContentCid, MAX_CONTENT_SIZE, MAX_LATENCY_MS, MIN_LATENCY_MS, PeerIdString,
9 TIMESTAMP_TOLERANCE_MS,
10};
11
12#[cfg(test)]
14use super::core::CHUNK_SIZE;
15use super::validation::ValidationError;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19#[cfg_attr(feature = "schema", derive(JsonSchema))]
20pub struct ChunkRequest {
21 pub content_cid: ContentCid,
23 pub chunk_index: u64,
25 pub challenge_nonce: [u8; 32],
27 pub requester_peer_id: PeerIdString,
29 pub requester_public_key: [u8; 32],
31 pub timestamp_ms: i64,
33}
34
35impl ChunkRequest {
36 #[must_use]
59 pub fn new(
60 content_cid: impl Into<String>,
61 chunk_index: u64,
62 challenge_nonce: [u8; 32],
63 requester_peer_id: impl Into<String>,
64 requester_public_key: [u8; 32],
65 ) -> Self {
66 Self {
67 content_cid: content_cid.into(),
68 chunk_index,
69 challenge_nonce,
70 requester_peer_id: requester_peer_id.into(),
71 requester_public_key,
72 timestamp_ms: chrono::Utc::now().timestamp_millis(),
73 }
74 }
75
76 #[must_use]
78 pub fn is_timestamp_valid(&self) -> bool {
79 let now_ms = chrono::Utc::now().timestamp_millis();
80 let age_ms = now_ms - self.timestamp_ms;
81 (0..=TIMESTAMP_TOLERANCE_MS).contains(&age_ms)
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87#[cfg_attr(feature = "schema", derive(JsonSchema))]
88pub struct ChunkResponse {
89 pub encrypted_chunk: Vec<u8>,
91 pub chunk_hash: [u8; 32],
93 pub provider_signature: Vec<u8>,
95 pub provider_public_key: [u8; 32],
97 pub challenge_echo: [u8; 32],
99 pub timestamp_ms: i64,
101}
102
103impl ChunkResponse {
104 #[must_use]
129 pub fn new(
130 encrypted_chunk: Vec<u8>,
131 chunk_hash: [u8; 32],
132 provider_signature: Vec<u8>,
133 provider_public_key: [u8; 32],
134 challenge_echo: [u8; 32],
135 ) -> Self {
136 Self {
137 encrypted_chunk,
138 chunk_hash,
139 provider_signature,
140 provider_public_key,
141 challenge_echo,
142 timestamp_ms: chrono::Utc::now().timestamp_millis(),
143 }
144 }
145
146 #[must_use]
148 pub fn chunk_size(&self) -> usize {
149 self.encrypted_chunk.len()
150 }
151
152 #[must_use]
154 pub fn verify_challenge_echo(&self, expected_nonce: &[u8; 32]) -> bool {
155 &self.challenge_echo == expected_nonce
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161#[cfg_attr(feature = "schema", derive(JsonSchema))]
162pub struct BandwidthProof {
163 pub session_id: uuid::Uuid,
165 pub content_cid: ContentCid,
167 pub chunk_index: u64,
169 pub bytes_transferred: Bytes,
171 pub provider_peer_id: PeerIdString,
173 pub requester_peer_id: PeerIdString,
175 pub provider_public_key: Vec<u8>,
177 pub requester_public_key: Vec<u8>,
179 pub provider_signature: Vec<u8>,
181 pub requester_signature: Vec<u8>,
183 pub challenge_nonce: Vec<u8>,
185 pub chunk_hash: Vec<u8>,
187 pub start_timestamp_ms: i64,
189 pub end_timestamp_ms: i64,
191 pub latency_ms: u32,
193}
194
195impl BandwidthProof {
196 #[must_use]
198 pub fn provider_sign_message(&self) -> Vec<u8> {
199 let mut msg = Vec::new();
200 msg.extend_from_slice(&self.challenge_nonce);
201 msg.extend_from_slice(&self.chunk_hash);
202 msg.extend_from_slice(&self.requester_public_key);
203 msg
204 }
205
206 #[must_use]
208 pub fn requester_sign_message(&self) -> Vec<u8> {
209 let mut msg = Vec::new();
210 msg.extend_from_slice(&self.challenge_nonce);
211 msg.extend_from_slice(&self.chunk_hash);
212 msg.extend_from_slice(&self.provider_public_key);
213 msg.extend_from_slice(&self.provider_signature);
214 msg
215 }
216
217 pub fn validate(&self) -> Result<(), Vec<ValidationError>> {
267 let mut errors = Vec::new();
268
269 if self.provider_public_key.len() != 32 {
271 errors.push(ValidationError::InvalidPublicKeyLength {
272 expected: 32,
273 actual: self.provider_public_key.len(),
274 });
275 }
276 if self.requester_public_key.len() != 32 {
277 errors.push(ValidationError::InvalidPublicKeyLength {
278 expected: 32,
279 actual: self.requester_public_key.len(),
280 });
281 }
282
283 if self.provider_signature.len() != 64 {
285 errors.push(ValidationError::InvalidSignatureLength {
286 expected: 64,
287 actual: self.provider_signature.len(),
288 });
289 }
290 if self.requester_signature.len() != 64 {
291 errors.push(ValidationError::InvalidSignatureLength {
292 expected: 64,
293 actual: self.requester_signature.len(),
294 });
295 }
296
297 if self.challenge_nonce.len() != 32 {
299 errors.push(ValidationError::InvalidNonceLength {
300 expected: 32,
301 actual: self.challenge_nonce.len(),
302 });
303 }
304
305 if self.chunk_hash.len() != 32 {
307 errors.push(ValidationError::InvalidHashLength {
308 expected: 32,
309 actual: self.chunk_hash.len(),
310 });
311 }
312
313 if self.content_cid.is_empty() {
315 errors.push(ValidationError::EmptyCid);
316 }
317
318 if self.provider_peer_id == self.requester_peer_id {
320 errors.push(ValidationError::SelfTransfer);
321 }
322
323 if self.start_timestamp_ms > self.end_timestamp_ms {
325 errors.push(ValidationError::InvalidTimestampOrder {
326 start_ms: self.start_timestamp_ms,
327 end_ms: self.end_timestamp_ms,
328 });
329 }
330
331 if self.latency_ms < MIN_LATENCY_MS {
333 errors.push(ValidationError::LatencyTooLow {
334 latency_ms: self.latency_ms,
335 min_ms: MIN_LATENCY_MS,
336 });
337 }
338 if self.latency_ms > MAX_LATENCY_MS {
339 errors.push(ValidationError::LatencyTooHigh {
340 latency_ms: self.latency_ms,
341 max_ms: MAX_LATENCY_MS,
342 });
343 }
344
345 let calculated_latency = self.end_timestamp_ms - self.start_timestamp_ms;
347 let latency_diff = (calculated_latency - i64::from(self.latency_ms)).abs();
348 if latency_diff > 100 {
349 errors.push(ValidationError::LatencyMismatch {
351 calculated_ms: calculated_latency,
352 reported_ms: self.latency_ms,
353 });
354 }
355
356 if self.bytes_transferred > MAX_CONTENT_SIZE {
358 errors.push(ValidationError::BytesExceedMax {
359 bytes: self.bytes_transferred,
360 max: MAX_CONTENT_SIZE,
361 });
362 }
363
364 if errors.is_empty() {
365 Ok(())
366 } else {
367 Err(errors)
368 }
369 }
370
371 pub fn validate_timestamp(&self, now_ms: i64) -> Result<(), ValidationError> {
379 if self.end_timestamp_ms > now_ms {
380 return Err(ValidationError::TimestampInFuture {
381 timestamp_ms: self.end_timestamp_ms,
382 now_ms,
383 });
384 }
385 if now_ms - self.end_timestamp_ms > TIMESTAMP_TOLERANCE_MS {
386 return Err(ValidationError::TimestampTooOld {
387 timestamp_ms: self.end_timestamp_ms,
388 now_ms,
389 tolerance_ms: TIMESTAMP_TOLERANCE_MS,
390 });
391 }
392 Ok(())
393 }
394
395 #[must_use]
397 pub fn is_valid(&self) -> bool {
398 self.validate().is_ok()
399 }
400
401 #[must_use]
403 #[allow(clippy::cast_precision_loss)]
404 pub fn bandwidth_bps(&self) -> f64 {
405 if self.latency_ms == 0 {
406 return 0.0;
407 }
408 (self.bytes_transferred as f64 * 1000.0) / f64::from(self.latency_ms)
409 }
410
411 #[must_use]
413 pub fn meets_quality_threshold(&self) -> bool {
415 self.latency_ms <= 500
416 }
417
418 #[must_use]
462 pub fn quality_multiplier(&self) -> f64 {
463 if self.latency_ms <= 500 { 1.0 } else { 0.5 }
464 }
465}
466
467#[derive(Debug, Default)]
469pub struct BandwidthProofBuilder {
470 session_id: Option<uuid::Uuid>,
471 content_cid: Option<ContentCid>,
472 chunk_index: u64,
473 bytes_transferred: Bytes,
474 provider_peer_id: Option<PeerIdString>,
475 requester_peer_id: Option<PeerIdString>,
476 provider_public_key: Option<Vec<u8>>,
477 requester_public_key: Option<Vec<u8>>,
478 provider_signature: Option<Vec<u8>>,
479 requester_signature: Option<Vec<u8>>,
480 challenge_nonce: Option<Vec<u8>>,
481 chunk_hash: Option<Vec<u8>>,
482 start_timestamp_ms: i64,
483 end_timestamp_ms: i64,
484 latency_ms: u32,
485}
486
487impl BandwidthProofBuilder {
488 #[must_use]
517 pub fn new() -> Self {
518 Self::default()
519 }
520
521 #[must_use]
523 pub fn session_id(mut self, id: uuid::Uuid) -> Self {
524 self.session_id = Some(id);
525 self
526 }
527
528 #[must_use]
530 pub fn content_cid(mut self, cid: impl Into<String>) -> Self {
531 self.content_cid = Some(cid.into());
532 self
533 }
534
535 #[must_use]
537 pub fn chunk_index(mut self, index: u64) -> Self {
538 self.chunk_index = index;
539 self
540 }
541
542 #[must_use]
544 pub fn bytes_transferred(mut self, bytes: Bytes) -> Self {
545 self.bytes_transferred = bytes;
546 self
547 }
548
549 #[must_use]
551 pub fn provider_peer_id(mut self, peer_id: impl Into<String>) -> Self {
552 self.provider_peer_id = Some(peer_id.into());
553 self
554 }
555
556 #[must_use]
558 pub fn requester_peer_id(mut self, peer_id: impl Into<String>) -> Self {
559 self.requester_peer_id = Some(peer_id.into());
560 self
561 }
562
563 #[must_use]
565 pub fn provider_public_key(mut self, key: impl Into<Vec<u8>>) -> Self {
566 self.provider_public_key = Some(key.into());
567 self
568 }
569
570 #[must_use]
572 pub fn requester_public_key(mut self, key: impl Into<Vec<u8>>) -> Self {
573 self.requester_public_key = Some(key.into());
574 self
575 }
576
577 #[must_use]
579 pub fn provider_signature(mut self, sig: impl Into<Vec<u8>>) -> Self {
580 self.provider_signature = Some(sig.into());
581 self
582 }
583
584 #[must_use]
586 pub fn requester_signature(mut self, sig: impl Into<Vec<u8>>) -> Self {
587 self.requester_signature = Some(sig.into());
588 self
589 }
590
591 #[must_use]
593 pub fn challenge_nonce(mut self, nonce: impl Into<Vec<u8>>) -> Self {
594 self.challenge_nonce = Some(nonce.into());
595 self
596 }
597
598 #[must_use]
600 pub fn chunk_hash(mut self, hash: impl Into<Vec<u8>>) -> Self {
601 self.chunk_hash = Some(hash.into());
602 self
603 }
604
605 #[must_use]
607 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
608 pub fn timestamps(mut self, start_ms: i64, end_ms: i64) -> Self {
609 self.start_timestamp_ms = start_ms;
610 self.end_timestamp_ms = end_ms;
611 self.latency_ms = (end_ms - start_ms).max(0) as u32;
612 self
613 }
614
615 #[must_use]
617 pub fn latency_ms(mut self, latency: u32) -> Self {
618 self.latency_ms = latency;
619 self
620 }
621
622 pub fn build(self) -> Result<BandwidthProof, &'static str> {
633 Ok(BandwidthProof {
634 session_id: self.session_id.unwrap_or_else(uuid::Uuid::new_v4),
635 content_cid: self.content_cid.ok_or("content_cid is required")?,
636 chunk_index: self.chunk_index,
637 bytes_transferred: self.bytes_transferred,
638 provider_peer_id: self
639 .provider_peer_id
640 .ok_or("provider_peer_id is required")?,
641 requester_peer_id: self
642 .requester_peer_id
643 .ok_or("requester_peer_id is required")?,
644 provider_public_key: self
645 .provider_public_key
646 .ok_or("provider_public_key is required")?,
647 requester_public_key: self
648 .requester_public_key
649 .ok_or("requester_public_key is required")?,
650 provider_signature: self
651 .provider_signature
652 .ok_or("provider_signature is required")?,
653 requester_signature: self
654 .requester_signature
655 .ok_or("requester_signature is required")?,
656 challenge_nonce: self.challenge_nonce.ok_or("challenge_nonce is required")?,
657 chunk_hash: self.chunk_hash.ok_or("chunk_hash is required")?,
658 start_timestamp_ms: self.start_timestamp_ms,
659 end_timestamp_ms: self.end_timestamp_ms,
660 latency_ms: self.latency_ms,
661 })
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use super::*;
668
669 #[test]
671 fn test_bandwidth_proof_builder() {
672 let proof = BandwidthProofBuilder::new()
673 .content_cid("QmTest123")
674 .chunk_index(0)
675 .bytes_transferred(CHUNK_SIZE as u64)
676 .provider_peer_id("12D3KooProvider")
677 .requester_peer_id("12D3KooRequester")
678 .provider_public_key(vec![1u8; 32])
679 .requester_public_key(vec![2u8; 32])
680 .provider_signature(vec![3u8; 64])
681 .requester_signature(vec![4u8; 64])
682 .challenge_nonce(vec![5u8; 32])
683 .chunk_hash(vec![6u8; 32])
684 .timestamps(1000, 1100)
685 .build()
686 .unwrap();
687
688 assert_eq!(proof.content_cid, "QmTest123");
689 assert_eq!(proof.chunk_index, 0);
690 assert_eq!(proof.bytes_transferred, CHUNK_SIZE as u64);
691 assert_eq!(proof.latency_ms, 100);
692 assert!(proof.is_valid());
693 }
694
695 #[test]
696 fn test_bandwidth_proof_validation_invalid_key_length() {
697 let proof = BandwidthProofBuilder::new()
698 .content_cid("QmTest")
699 .provider_peer_id("provider")
700 .requester_peer_id("requester")
701 .provider_public_key(vec![1u8; 16]) .requester_public_key(vec![2u8; 32])
703 .provider_signature(vec![3u8; 64])
704 .requester_signature(vec![4u8; 64])
705 .challenge_nonce(vec![5u8; 32])
706 .chunk_hash(vec![6u8; 32])
707 .timestamps(1000, 1100)
708 .build()
709 .unwrap();
710
711 let result = proof.validate();
712 assert!(result.is_err());
713 let errors = result.unwrap_err();
714 assert!(errors.iter().any(|e| matches!(
715 e,
716 ValidationError::InvalidPublicKeyLength {
717 expected: 32,
718 actual: 16
719 }
720 )));
721 }
722
723 #[test]
724 fn test_bandwidth_proof_validation_self_transfer() {
725 let proof = BandwidthProofBuilder::new()
726 .content_cid("QmTest")
727 .provider_peer_id("same_peer")
728 .requester_peer_id("same_peer")
729 .provider_public_key(vec![1u8; 32])
730 .requester_public_key(vec![2u8; 32])
731 .provider_signature(vec![3u8; 64])
732 .requester_signature(vec![4u8; 64])
733 .challenge_nonce(vec![5u8; 32])
734 .chunk_hash(vec![6u8; 32])
735 .timestamps(1000, 1100)
736 .build()
737 .unwrap();
738
739 let result = proof.validate();
740 assert!(result.is_err());
741 let errors = result.unwrap_err();
742 assert!(
743 errors
744 .iter()
745 .any(|e| matches!(e, ValidationError::SelfTransfer))
746 );
747 }
748
749 #[test]
750 fn test_bandwidth_proof_validation_invalid_timestamp_order() {
751 let proof = BandwidthProofBuilder::new()
752 .content_cid("QmTest")
753 .provider_peer_id("provider")
754 .requester_peer_id("requester")
755 .provider_public_key(vec![1u8; 32])
756 .requester_public_key(vec![2u8; 32])
757 .provider_signature(vec![3u8; 64])
758 .requester_signature(vec![4u8; 64])
759 .challenge_nonce(vec![5u8; 32])
760 .chunk_hash(vec![6u8; 32])
761 .timestamps(2000, 1000) .build()
763 .unwrap();
764
765 let result = proof.validate();
766 assert!(result.is_err());
767 let errors = result.unwrap_err();
768 assert!(
769 errors
770 .iter()
771 .any(|e| matches!(e, ValidationError::InvalidTimestampOrder { .. }))
772 );
773 }
774
775 #[test]
776 fn test_bandwidth_proof_validation_latency_too_low() {
777 let proof = BandwidthProofBuilder::new()
778 .content_cid("QmTest")
779 .provider_peer_id("provider")
780 .requester_peer_id("requester")
781 .provider_public_key(vec![1u8; 32])
782 .requester_public_key(vec![2u8; 32])
783 .provider_signature(vec![3u8; 64])
784 .requester_signature(vec![4u8; 64])
785 .challenge_nonce(vec![5u8; 32])
786 .chunk_hash(vec![6u8; 32])
787 .timestamps(1000, 1001)
788 .latency_ms(0) .build()
790 .unwrap();
791
792 let result = proof.validate();
793 assert!(result.is_err());
794 let errors = result.unwrap_err();
795 assert!(
796 errors
797 .iter()
798 .any(|e| matches!(e, ValidationError::LatencyTooLow { .. }))
799 );
800 }
801
802 #[test]
803 fn test_bandwidth_proof_validation_latency_too_high() {
804 let proof = BandwidthProofBuilder::new()
805 .content_cid("QmTest")
806 .provider_peer_id("provider")
807 .requester_peer_id("requester")
808 .provider_public_key(vec![1u8; 32])
809 .requester_public_key(vec![2u8; 32])
810 .provider_signature(vec![3u8; 64])
811 .requester_signature(vec![4u8; 64])
812 .challenge_nonce(vec![5u8; 32])
813 .chunk_hash(vec![6u8; 32])
814 .timestamps(1000, 1100)
815 .latency_ms(40_000) .build()
817 .unwrap();
818
819 let result = proof.validate();
820 assert!(result.is_err());
821 let errors = result.unwrap_err();
822 assert!(
823 errors
824 .iter()
825 .any(|e| matches!(e, ValidationError::LatencyTooHigh { .. }))
826 );
827 }
828
829 #[test]
830 fn test_bandwidth_proof_timestamp_validation() {
831 let now_ms = chrono::Utc::now().timestamp_millis();
832
833 let proof = BandwidthProofBuilder::new()
834 .content_cid("QmTest")
835 .provider_peer_id("provider")
836 .requester_peer_id("requester")
837 .provider_public_key(vec![1u8; 32])
838 .requester_public_key(vec![2u8; 32])
839 .provider_signature(vec![3u8; 64])
840 .requester_signature(vec![4u8; 64])
841 .challenge_nonce(vec![5u8; 32])
842 .chunk_hash(vec![6u8; 32])
843 .timestamps(now_ms - 1000, now_ms - 900)
844 .build()
845 .unwrap();
846
847 assert!(proof.validate_timestamp(now_ms).is_ok());
849
850 assert!(proof.validate_timestamp(now_ms - 2000).is_err());
852
853 assert!(
855 proof
856 .validate_timestamp(now_ms + TIMESTAMP_TOLERANCE_MS + 1000)
857 .is_err()
858 );
859 }
860
861 #[test]
862 fn test_bandwidth_proof_sign_messages() {
863 let proof = BandwidthProofBuilder::new()
864 .content_cid("QmTest")
865 .provider_peer_id("provider")
866 .requester_peer_id("requester")
867 .provider_public_key(vec![1u8; 32])
868 .requester_public_key(vec![2u8; 32])
869 .provider_signature(vec![3u8; 64])
870 .requester_signature(vec![4u8; 64])
871 .challenge_nonce(vec![5u8; 32])
872 .chunk_hash(vec![6u8; 32])
873 .timestamps(1000, 1100)
874 .build()
875 .unwrap();
876
877 let provider_msg = proof.provider_sign_message();
878 assert_eq!(provider_msg.len(), 32 + 32 + 32); let requester_msg = proof.requester_sign_message();
881 assert_eq!(requester_msg.len(), 32 + 32 + 32 + 64); }
883
884 #[test]
885 fn test_bandwidth_proof_serialization() {
886 let proof = BandwidthProofBuilder::new()
887 .content_cid("QmTest")
888 .provider_peer_id("provider")
889 .requester_peer_id("requester")
890 .provider_public_key(vec![1u8; 32])
891 .requester_public_key(vec![2u8; 32])
892 .provider_signature(vec![3u8; 64])
893 .requester_signature(vec![4u8; 64])
894 .challenge_nonce(vec![5u8; 32])
895 .chunk_hash(vec![6u8; 32])
896 .timestamps(1000, 1100)
897 .build()
898 .unwrap();
899
900 let json = serde_json::to_string(&proof).unwrap();
901 let deserialized: BandwidthProof = serde_json::from_str(&json).unwrap();
902 assert_eq!(proof.content_cid, deserialized.content_cid);
903 assert_eq!(proof.chunk_index, deserialized.chunk_index);
904 }
905
906 #[test]
907 fn test_chunk_request_serialization() {
908 let request = ChunkRequest {
909 content_cid: "QmTest".to_string(),
910 chunk_index: 0,
911 challenge_nonce: [1u8; 32],
912 requester_peer_id: "12D3Koo".to_string(),
913 requester_public_key: [2u8; 32],
914 timestamp_ms: 1000,
915 };
916
917 let json = serde_json::to_string(&request).unwrap();
918 let deserialized: ChunkRequest = serde_json::from_str(&json).unwrap();
919 assert_eq!(request.content_cid, deserialized.content_cid);
920 assert_eq!(request.chunk_index, deserialized.chunk_index);
921 }
922
923 #[test]
924 fn test_chunk_response_serialization() {
925 let response = ChunkResponse {
926 encrypted_chunk: vec![1, 2, 3],
927 chunk_hash: [4u8; 32],
928 provider_signature: vec![5u8; 64],
929 provider_public_key: [6u8; 32],
930 challenge_echo: [7u8; 32],
931 timestamp_ms: 2000,
932 };
933
934 let json = serde_json::to_string(&response).unwrap();
935 let deserialized: ChunkResponse = serde_json::from_str(&json).unwrap();
936 assert_eq!(response.encrypted_chunk, deserialized.encrypted_chunk);
937 }
938
939 #[test]
940 fn test_chunk_request_timestamp_validation() {
941 let request = ChunkRequest::new("QmTest", 0, [1u8; 32], "12D3Koo", [2u8; 32]);
942
943 assert!(request.is_timestamp_valid());
945 }
946
947 #[test]
948 fn test_chunk_response_verify_challenge_echo() {
949 let nonce = [42u8; 32];
950 let response =
951 ChunkResponse::new(vec![1, 2, 3], [4u8; 32], vec![5u8; 64], [6u8; 32], nonce);
952
953 assert!(response.verify_challenge_echo(&nonce));
954 assert!(!response.verify_challenge_echo(&[0u8; 32]));
955 }
956
957 #[test]
958 fn test_chunk_response_chunk_size() {
959 let data = vec![1u8; CHUNK_SIZE];
960 let response =
961 ChunkResponse::new(data.clone(), [4u8; 32], vec![5u8; 64], [6u8; 32], [7u8; 32]);
962
963 assert_eq!(response.chunk_size(), CHUNK_SIZE);
964 }
965
966 #[test]
967 fn test_bandwidth_proof_bandwidth_bps() {
968 let proof = BandwidthProofBuilder::new()
969 .content_cid("QmTest")
970 .provider_peer_id("provider")
971 .requester_peer_id("requester")
972 .provider_public_key(vec![1u8; 32])
973 .requester_public_key(vec![2u8; 32])
974 .provider_signature(vec![3u8; 64])
975 .requester_signature(vec![4u8; 64])
976 .challenge_nonce(vec![5u8; 32])
977 .chunk_hash(vec![6u8; 32])
978 .bytes_transferred(1000)
979 .timestamps(1000, 1100) .build()
981 .unwrap();
982
983 assert_eq!(proof.bandwidth_bps(), 10_000.0);
985 }
986
987 #[test]
988 fn test_bandwidth_proof_quality_threshold() {
989 let proof_good = BandwidthProofBuilder::new()
990 .content_cid("QmTest")
991 .provider_peer_id("provider")
992 .requester_peer_id("requester")
993 .provider_public_key(vec![1u8; 32])
994 .requester_public_key(vec![2u8; 32])
995 .provider_signature(vec![3u8; 64])
996 .requester_signature(vec![4u8; 64])
997 .challenge_nonce(vec![5u8; 32])
998 .chunk_hash(vec![6u8; 32])
999 .timestamps(1000, 1400) .build()
1001 .unwrap();
1002
1003 assert!(proof_good.meets_quality_threshold());
1004 assert_eq!(proof_good.quality_multiplier(), 1.0);
1005
1006 let proof_bad = BandwidthProofBuilder::new()
1007 .content_cid("QmTest")
1008 .provider_peer_id("provider")
1009 .requester_peer_id("requester")
1010 .provider_public_key(vec![1u8; 32])
1011 .requester_public_key(vec![2u8; 32])
1012 .provider_signature(vec![3u8; 64])
1013 .requester_signature(vec![4u8; 64])
1014 .challenge_nonce(vec![5u8; 32])
1015 .chunk_hash(vec![6u8; 32])
1016 .timestamps(1000, 1600) .build()
1018 .unwrap();
1019
1020 assert!(!proof_bad.meets_quality_threshold());
1021 assert_eq!(proof_bad.quality_multiplier(), 0.5);
1022 }
1023}
1024
1025#[cfg(test)]
1027pub mod test_helpers {
1028 use super::*;
1029 use crate::types::content::ContentMetadataBuilder;
1030 use crate::types::enums::ContentCategory;
1031
1032 #[must_use]
1034 pub fn create_test_proof() -> BandwidthProof {
1035 BandwidthProofBuilder::new()
1036 .content_cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
1037 .chunk_index(0)
1038 .bytes_transferred(CHUNK_SIZE as u64)
1039 .provider_peer_id("12D3KooWProviderPeerTestID")
1040 .requester_peer_id("12D3KooWRequesterPeerTestID")
1041 .provider_public_key(vec![1u8; 32])
1042 .requester_public_key(vec![2u8; 32])
1043 .provider_signature(vec![3u8; 64])
1044 .requester_signature(vec![4u8; 64])
1045 .challenge_nonce(vec![5u8; 32])
1046 .chunk_hash(vec![6u8; 32])
1047 .timestamps(1000, 1100)
1048 .build()
1049 .unwrap()
1050 }
1051
1052 #[must_use]
1054 pub fn create_test_content() -> crate::types::content::ContentMetadata {
1055 ContentMetadataBuilder::new()
1056 .cid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")
1057 .title("Test Content")
1058 .description("A test content item")
1059 .category(ContentCategory::ThreeDModels)
1060 .size_bytes(5 * 1024 * 1024)
1061 .price(1000)
1062 .creator_id(uuid::Uuid::new_v4())
1063 .build()
1064 .unwrap()
1065 }
1066
1067 #[must_use]
1069 pub fn create_test_chunk_request() -> ChunkRequest {
1070 ChunkRequest::new(
1071 "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
1072 0,
1073 [1u8; 32],
1074 "12D3KooWRequesterPeerTestID",
1075 [2u8; 32],
1076 )
1077 }
1078
1079 #[must_use]
1081 pub fn create_test_chunk_response() -> ChunkResponse {
1082 ChunkResponse::new(
1083 vec![0u8; CHUNK_SIZE],
1084 [3u8; 32],
1085 vec![4u8; 64],
1086 [5u8; 32],
1087 [1u8; 32],
1088 )
1089 }
1090}