1use bytes::Bytes;
10use serde::{Deserialize, Serialize};
11
12pub const CHUNK_PROTOCOL_ID: &str = "autonomi.ant.chunk.v1";
14
15pub const PROTOCOL_VERSION: u16 = 1;
17
18pub const MAX_CHUNK_SIZE: usize = 4 * 1024 * 1024;
20
21pub const MAX_WIRE_MESSAGE_SIZE: usize = 5 * 1024 * 1024;
27
28pub const DATA_TYPE_CHUNK: u32 = 0;
30
31pub const CLOSE_GROUP_SIZE: usize = 7;
36
37pub const CLOSE_GROUP_MAJORITY: usize = (CLOSE_GROUP_SIZE / 2) + 1;
41
42pub type XorName = [u8; 32];
44
45pub const XORNAME_LEN: usize = std::mem::size_of::<XorName>();
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
55#[non_exhaustive]
56pub enum ChunkMessageBody {
57 PutRequest(ChunkPutRequest),
59 PutResponse(ChunkPutResponse),
61 GetRequest(ChunkGetRequest),
63 GetResponse(ChunkGetResponse),
65 QuoteRequest(ChunkQuoteRequest),
67 QuoteResponse(ChunkQuoteResponse),
69 MerkleCandidateQuoteRequest(MerkleCandidateQuoteRequest),
71 MerkleCandidateQuoteResponse(MerkleCandidateQuoteResponse),
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct ChunkMessage {
83 pub request_id: u64,
85 pub body: ChunkMessageBody,
87}
88
89impl ChunkMessage {
90 pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
96 postcard::to_stdvec(self).map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
97 }
98
99 pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
110 if data.len() > MAX_WIRE_MESSAGE_SIZE {
111 return Err(ProtocolError::MessageTooLarge {
112 size: data.len(),
113 max_size: MAX_WIRE_MESSAGE_SIZE,
114 });
115 }
116 postcard::from_bytes(data).map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct ChunkPutRequest {
133 pub address: XorName,
135 pub content: Bytes,
137 pub payment_proof: Option<Vec<u8>>,
140}
141
142impl ChunkPutRequest {
143 #[must_use]
145 pub fn new(address: XorName, content: Bytes) -> Self {
146 Self {
147 address,
148 content,
149 payment_proof: None,
150 }
151 }
152
153 #[must_use]
155 pub fn with_payment(address: XorName, content: Bytes, payment_proof: Vec<u8>) -> Self {
156 Self {
157 address,
158 content,
159 payment_proof: Some(payment_proof),
160 }
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166#[non_exhaustive]
167pub enum ChunkPutResponse {
168 Success {
170 address: XorName,
172 },
173 AlreadyExists {
175 address: XorName,
177 },
178 PaymentRequired {
180 message: String,
182 },
183 Error(ProtocolError),
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct ChunkGetRequest {
194 pub address: XorName,
196}
197
198impl ChunkGetRequest {
199 #[must_use]
201 pub fn new(address: XorName) -> Self {
202 Self { address }
203 }
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208#[non_exhaustive]
209pub enum ChunkGetResponse {
210 Success {
212 address: XorName,
214 content: Vec<u8>,
216 },
217 NotFound {
219 address: XorName,
221 },
222 Error(ProtocolError),
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct ChunkQuoteRequest {
233 pub address: XorName,
235 pub data_size: u64,
237 pub data_type: u32,
239}
240
241impl ChunkQuoteRequest {
242 #[must_use]
244 pub fn new(address: XorName, data_size: u64) -> Self {
245 Self {
246 address,
247 data_size,
248 data_type: DATA_TYPE_CHUNK,
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255#[non_exhaustive]
256pub enum ChunkQuoteResponse {
257 Success {
263 quote: Vec<u8>,
265 already_stored: bool,
267 #[serde(default)]
284 commitment: Option<Vec<u8>>,
285 },
286 Error(ProtocolError),
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct MerkleCandidateQuoteRequest {
300 pub address: XorName,
302 pub data_type: u32,
304 pub data_size: u64,
306 pub merkle_payment_timestamp: u64,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312#[non_exhaustive]
313pub enum MerkleCandidateQuoteResponse {
314 Success {
317 candidate_node: Vec<u8>,
319 #[serde(default)]
331 commitment: Option<Vec<u8>>,
332 },
333 Error(ProtocolError),
335}
336
337pub const PROOF_TAG_SINGLE_NODE: u8 = 0x01;
344pub const PROOF_TAG_MERKLE: u8 = 0x02;
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
353#[non_exhaustive]
354pub enum ProtocolError {
355 SerializationFailed(String),
357 DeserializationFailed(String),
359 MessageTooLarge {
361 size: usize,
363 max_size: usize,
365 },
366 ChunkTooLarge {
368 size: usize,
370 max_size: usize,
372 },
373 AddressMismatch {
375 expected: XorName,
377 actual: XorName,
379 },
380 StorageFailed(String),
382 PaymentFailed(String),
384 QuoteFailed(String),
386 Internal(String),
388}
389
390impl std::fmt::Display for ProtocolError {
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392 match self {
393 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
394 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
395 Self::MessageTooLarge { size, max_size } => {
396 write!(f, "message size {size} exceeds maximum {max_size}")
397 }
398 Self::ChunkTooLarge { size, max_size } => {
399 write!(f, "chunk size {size} exceeds maximum {max_size}")
400 }
401 Self::AddressMismatch { expected, actual } => {
402 write!(
403 f,
404 "address mismatch: expected {}, got {}",
405 hex::encode(expected),
406 hex::encode(actual)
407 )
408 }
409 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
410 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
411 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
412 Self::Internal(msg) => write!(f, "internal error: {msg}"),
413 }
414 }
415}
416
417impl std::error::Error for ProtocolError {}
418
419#[cfg(test)]
420#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_put_request_encode_decode() {
426 let address = [0xAB; 32];
427 let content = Bytes::from_static(&[1, 2, 3, 4, 5]);
428 let request = ChunkPutRequest::new(address, content.clone());
429 let msg = ChunkMessage {
430 request_id: 42,
431 body: ChunkMessageBody::PutRequest(request),
432 };
433
434 let encoded = msg.encode().expect("encode should succeed");
435 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
436
437 assert_eq!(decoded.request_id, 42);
438 if let ChunkMessageBody::PutRequest(req) = decoded.body {
439 assert_eq!(req.address, address);
440 assert_eq!(req.content, content);
441 assert!(req.payment_proof.is_none());
442 } else {
443 panic!("expected PutRequest");
444 }
445 }
446
447 #[test]
448 fn test_put_request_with_payment() {
449 let address = [0xAB; 32];
450 let content = Bytes::from_static(&[1, 2, 3, 4, 5]);
451 let payment = vec![10, 20, 30];
452 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
453
454 assert_eq!(request.address, address);
455 assert_eq!(request.content, content);
456 assert_eq!(request.payment_proof, Some(payment));
457 }
458
459 #[test]
460 fn test_get_request_encode_decode() {
461 let address = [0xCD; 32];
462 let request = ChunkGetRequest::new(address);
463 let msg = ChunkMessage {
464 request_id: 7,
465 body: ChunkMessageBody::GetRequest(request),
466 };
467
468 let encoded = msg.encode().expect("encode should succeed");
469 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
470
471 assert_eq!(decoded.request_id, 7);
472 if let ChunkMessageBody::GetRequest(req) = decoded.body {
473 assert_eq!(req.address, address);
474 } else {
475 panic!("expected GetRequest");
476 }
477 }
478
479 #[test]
480 fn test_put_response_success() {
481 let address = [0xEF; 32];
482 let response = ChunkPutResponse::Success { address };
483 let msg = ChunkMessage {
484 request_id: 99,
485 body: ChunkMessageBody::PutResponse(response),
486 };
487
488 let encoded = msg.encode().expect("encode should succeed");
489 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
490
491 assert_eq!(decoded.request_id, 99);
492 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
493 decoded.body
494 {
495 assert_eq!(addr, address);
496 } else {
497 panic!("expected PutResponse::Success");
498 }
499 }
500
501 #[test]
502 fn test_get_response_not_found() {
503 let address = [0x12; 32];
504 let response = ChunkGetResponse::NotFound { address };
505 let msg = ChunkMessage {
506 request_id: 0,
507 body: ChunkMessageBody::GetResponse(response),
508 };
509
510 let encoded = msg.encode().expect("encode should succeed");
511 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
512
513 assert_eq!(decoded.request_id, 0);
514 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
515 decoded.body
516 {
517 assert_eq!(addr, address);
518 } else {
519 panic!("expected GetResponse::NotFound");
520 }
521 }
522
523 #[test]
524 fn test_quote_request_encode_decode() {
525 let address = [0x34; 32];
526 let request = ChunkQuoteRequest::new(address, 1024);
527 let msg = ChunkMessage {
528 request_id: 1,
529 body: ChunkMessageBody::QuoteRequest(request),
530 };
531
532 let encoded = msg.encode().expect("encode should succeed");
533 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
534
535 assert_eq!(decoded.request_id, 1);
536 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
537 assert_eq!(req.address, address);
538 assert_eq!(req.data_size, 1024);
539 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
540 } else {
541 panic!("expected QuoteRequest");
542 }
543 }
544
545 #[test]
546 fn test_protocol_error_display() {
547 let err = ProtocolError::ChunkTooLarge {
548 size: 5_000_000,
549 max_size: MAX_CHUNK_SIZE,
550 };
551 assert!(err.to_string().contains("5000000"));
552 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
553
554 let err = ProtocolError::AddressMismatch {
555 expected: [0xAA; 32],
556 actual: [0xBB; 32],
557 };
558 let display = err.to_string();
559 assert!(display.contains("address mismatch"));
560 }
561
562 #[test]
563 fn test_decode_rejects_oversized_payload() {
564 let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
565 let result = ChunkMessage::decode(&oversized);
566 assert!(result.is_err());
567 let err = result.unwrap_err();
568 assert!(
569 matches!(err, ProtocolError::MessageTooLarge { .. }),
570 "expected MessageTooLarge, got {err:?}"
571 );
572 }
573
574 #[test]
575 fn test_invalid_decode() {
576 let invalid_data = vec![0xFF, 0xFF, 0xFF];
577 let result = ChunkMessage::decode(&invalid_data);
578 assert!(result.is_err());
579 }
580
581 #[test]
582 fn test_constants() {
583 assert_eq!(CHUNK_PROTOCOL_ID, "autonomi.ant.chunk.v1");
584 assert_eq!(PROTOCOL_VERSION, 1);
585 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
586 assert_eq!(DATA_TYPE_CHUNK, 0);
587 }
588
589 #[test]
590 fn test_proof_tag_constants() {
591 assert_ne!(PROOF_TAG_SINGLE_NODE, PROOF_TAG_MERKLE);
593 assert_ne!(PROOF_TAG_SINGLE_NODE, 0x00);
594 assert_ne!(PROOF_TAG_MERKLE, 0x00);
595 assert_eq!(PROOF_TAG_SINGLE_NODE, 0x01);
596 assert_eq!(PROOF_TAG_MERKLE, 0x02);
597 }
598
599 #[test]
600 fn test_merkle_candidate_quote_request_encode_decode() {
601 let address = [0x56; 32];
602 let request = MerkleCandidateQuoteRequest {
603 address,
604 data_type: DATA_TYPE_CHUNK,
605 data_size: 2048,
606 merkle_payment_timestamp: 1_700_000_000,
607 };
608 let msg = ChunkMessage {
609 request_id: 500,
610 body: ChunkMessageBody::MerkleCandidateQuoteRequest(request),
611 };
612
613 let encoded = msg.encode().expect("encode should succeed");
614 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
615
616 assert_eq!(decoded.request_id, 500);
617 if let ChunkMessageBody::MerkleCandidateQuoteRequest(req) = decoded.body {
618 assert_eq!(req.address, address);
619 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
620 assert_eq!(req.data_size, 2048);
621 assert_eq!(req.merkle_payment_timestamp, 1_700_000_000);
622 } else {
623 panic!("expected MerkleCandidateQuoteRequest");
624 }
625 }
626
627 #[test]
628 fn test_merkle_candidate_quote_response_success_encode_decode() {
629 let candidate_node_bytes = vec![0xAA, 0xBB, 0xCC, 0xDD];
630 let response = MerkleCandidateQuoteResponse::Success {
631 candidate_node: candidate_node_bytes.clone(),
632 commitment: Some(vec![0x11, 0x22]),
633 };
634 let msg = ChunkMessage {
635 request_id: 501,
636 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
637 };
638
639 let encoded = msg.encode().expect("encode should succeed");
640 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
641
642 assert_eq!(decoded.request_id, 501);
643 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
644 MerkleCandidateQuoteResponse::Success {
645 candidate_node,
646 commitment,
647 },
648 ) = decoded.body
649 {
650 assert_eq!(candidate_node, candidate_node_bytes);
651 assert_eq!(commitment, Some(vec![0x11, 0x22]));
652 } else {
653 panic!("expected MerkleCandidateQuoteResponse::Success");
654 }
655 }
656
657 #[test]
658 fn test_merkle_candidate_quote_response_error_encode_decode() {
659 let error = ProtocolError::QuoteFailed("no libp2p keypair".to_string());
660 let response = MerkleCandidateQuoteResponse::Error(error.clone());
661 let msg = ChunkMessage {
662 request_id: 502,
663 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
664 };
665
666 let encoded = msg.encode().expect("encode should succeed");
667 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
668
669 assert_eq!(decoded.request_id, 502);
670 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
671 MerkleCandidateQuoteResponse::Error(err),
672 ) = decoded.body
673 {
674 assert_eq!(err, error);
675 } else {
676 panic!("expected MerkleCandidateQuoteResponse::Error");
677 }
678 }
679}