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 },
268 Error(ProtocolError),
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct MerkleCandidateQuoteRequest {
282 pub address: XorName,
284 pub data_type: u32,
286 pub data_size: u64,
288 pub merkle_payment_timestamp: u64,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294#[non_exhaustive]
295pub enum MerkleCandidateQuoteResponse {
296 Success {
299 candidate_node: Vec<u8>,
301 },
302 Error(ProtocolError),
304}
305
306pub const PROOF_TAG_SINGLE_NODE: u8 = 0x01;
313pub const PROOF_TAG_MERKLE: u8 = 0x02;
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
322#[non_exhaustive]
323pub enum ProtocolError {
324 SerializationFailed(String),
326 DeserializationFailed(String),
328 MessageTooLarge {
330 size: usize,
332 max_size: usize,
334 },
335 ChunkTooLarge {
337 size: usize,
339 max_size: usize,
341 },
342 AddressMismatch {
344 expected: XorName,
346 actual: XorName,
348 },
349 StorageFailed(String),
351 PaymentFailed(String),
353 QuoteFailed(String),
355 Internal(String),
357}
358
359impl std::fmt::Display for ProtocolError {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 match self {
362 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
363 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
364 Self::MessageTooLarge { size, max_size } => {
365 write!(f, "message size {size} exceeds maximum {max_size}")
366 }
367 Self::ChunkTooLarge { size, max_size } => {
368 write!(f, "chunk size {size} exceeds maximum {max_size}")
369 }
370 Self::AddressMismatch { expected, actual } => {
371 write!(
372 f,
373 "address mismatch: expected {}, got {}",
374 hex::encode(expected),
375 hex::encode(actual)
376 )
377 }
378 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
379 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
380 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
381 Self::Internal(msg) => write!(f, "internal error: {msg}"),
382 }
383 }
384}
385
386impl std::error::Error for ProtocolError {}
387
388#[cfg(test)]
389#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
390mod tests {
391 use super::*;
392
393 #[test]
394 fn test_put_request_encode_decode() {
395 let address = [0xAB; 32];
396 let content = Bytes::from_static(&[1, 2, 3, 4, 5]);
397 let request = ChunkPutRequest::new(address, content.clone());
398 let msg = ChunkMessage {
399 request_id: 42,
400 body: ChunkMessageBody::PutRequest(request),
401 };
402
403 let encoded = msg.encode().expect("encode should succeed");
404 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
405
406 assert_eq!(decoded.request_id, 42);
407 if let ChunkMessageBody::PutRequest(req) = decoded.body {
408 assert_eq!(req.address, address);
409 assert_eq!(req.content, content);
410 assert!(req.payment_proof.is_none());
411 } else {
412 panic!("expected PutRequest");
413 }
414 }
415
416 #[test]
417 fn test_put_request_with_payment() {
418 let address = [0xAB; 32];
419 let content = Bytes::from_static(&[1, 2, 3, 4, 5]);
420 let payment = vec![10, 20, 30];
421 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
422
423 assert_eq!(request.address, address);
424 assert_eq!(request.content, content);
425 assert_eq!(request.payment_proof, Some(payment));
426 }
427
428 #[test]
429 fn test_get_request_encode_decode() {
430 let address = [0xCD; 32];
431 let request = ChunkGetRequest::new(address);
432 let msg = ChunkMessage {
433 request_id: 7,
434 body: ChunkMessageBody::GetRequest(request),
435 };
436
437 let encoded = msg.encode().expect("encode should succeed");
438 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
439
440 assert_eq!(decoded.request_id, 7);
441 if let ChunkMessageBody::GetRequest(req) = decoded.body {
442 assert_eq!(req.address, address);
443 } else {
444 panic!("expected GetRequest");
445 }
446 }
447
448 #[test]
449 fn test_put_response_success() {
450 let address = [0xEF; 32];
451 let response = ChunkPutResponse::Success { address };
452 let msg = ChunkMessage {
453 request_id: 99,
454 body: ChunkMessageBody::PutResponse(response),
455 };
456
457 let encoded = msg.encode().expect("encode should succeed");
458 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
459
460 assert_eq!(decoded.request_id, 99);
461 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
462 decoded.body
463 {
464 assert_eq!(addr, address);
465 } else {
466 panic!("expected PutResponse::Success");
467 }
468 }
469
470 #[test]
471 fn test_get_response_not_found() {
472 let address = [0x12; 32];
473 let response = ChunkGetResponse::NotFound { address };
474 let msg = ChunkMessage {
475 request_id: 0,
476 body: ChunkMessageBody::GetResponse(response),
477 };
478
479 let encoded = msg.encode().expect("encode should succeed");
480 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
481
482 assert_eq!(decoded.request_id, 0);
483 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
484 decoded.body
485 {
486 assert_eq!(addr, address);
487 } else {
488 panic!("expected GetResponse::NotFound");
489 }
490 }
491
492 #[test]
493 fn test_quote_request_encode_decode() {
494 let address = [0x34; 32];
495 let request = ChunkQuoteRequest::new(address, 1024);
496 let msg = ChunkMessage {
497 request_id: 1,
498 body: ChunkMessageBody::QuoteRequest(request),
499 };
500
501 let encoded = msg.encode().expect("encode should succeed");
502 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
503
504 assert_eq!(decoded.request_id, 1);
505 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
506 assert_eq!(req.address, address);
507 assert_eq!(req.data_size, 1024);
508 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
509 } else {
510 panic!("expected QuoteRequest");
511 }
512 }
513
514 #[test]
515 fn test_protocol_error_display() {
516 let err = ProtocolError::ChunkTooLarge {
517 size: 5_000_000,
518 max_size: MAX_CHUNK_SIZE,
519 };
520 assert!(err.to_string().contains("5000000"));
521 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
522
523 let err = ProtocolError::AddressMismatch {
524 expected: [0xAA; 32],
525 actual: [0xBB; 32],
526 };
527 let display = err.to_string();
528 assert!(display.contains("address mismatch"));
529 }
530
531 #[test]
532 fn test_decode_rejects_oversized_payload() {
533 let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
534 let result = ChunkMessage::decode(&oversized);
535 assert!(result.is_err());
536 let err = result.unwrap_err();
537 assert!(
538 matches!(err, ProtocolError::MessageTooLarge { .. }),
539 "expected MessageTooLarge, got {err:?}"
540 );
541 }
542
543 #[test]
544 fn test_invalid_decode() {
545 let invalid_data = vec![0xFF, 0xFF, 0xFF];
546 let result = ChunkMessage::decode(&invalid_data);
547 assert!(result.is_err());
548 }
549
550 #[test]
551 fn test_constants() {
552 assert_eq!(CHUNK_PROTOCOL_ID, "autonomi.ant.chunk.v1");
553 assert_eq!(PROTOCOL_VERSION, 1);
554 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
555 assert_eq!(DATA_TYPE_CHUNK, 0);
556 }
557
558 #[test]
559 fn test_proof_tag_constants() {
560 assert_ne!(PROOF_TAG_SINGLE_NODE, PROOF_TAG_MERKLE);
562 assert_ne!(PROOF_TAG_SINGLE_NODE, 0x00);
563 assert_ne!(PROOF_TAG_MERKLE, 0x00);
564 assert_eq!(PROOF_TAG_SINGLE_NODE, 0x01);
565 assert_eq!(PROOF_TAG_MERKLE, 0x02);
566 }
567
568 #[test]
569 fn test_merkle_candidate_quote_request_encode_decode() {
570 let address = [0x56; 32];
571 let request = MerkleCandidateQuoteRequest {
572 address,
573 data_type: DATA_TYPE_CHUNK,
574 data_size: 2048,
575 merkle_payment_timestamp: 1_700_000_000,
576 };
577 let msg = ChunkMessage {
578 request_id: 500,
579 body: ChunkMessageBody::MerkleCandidateQuoteRequest(request),
580 };
581
582 let encoded = msg.encode().expect("encode should succeed");
583 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
584
585 assert_eq!(decoded.request_id, 500);
586 if let ChunkMessageBody::MerkleCandidateQuoteRequest(req) = decoded.body {
587 assert_eq!(req.address, address);
588 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
589 assert_eq!(req.data_size, 2048);
590 assert_eq!(req.merkle_payment_timestamp, 1_700_000_000);
591 } else {
592 panic!("expected MerkleCandidateQuoteRequest");
593 }
594 }
595
596 #[test]
597 fn test_merkle_candidate_quote_response_success_encode_decode() {
598 let candidate_node_bytes = vec![0xAA, 0xBB, 0xCC, 0xDD];
599 let response = MerkleCandidateQuoteResponse::Success {
600 candidate_node: candidate_node_bytes.clone(),
601 };
602 let msg = ChunkMessage {
603 request_id: 501,
604 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
605 };
606
607 let encoded = msg.encode().expect("encode should succeed");
608 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
609
610 assert_eq!(decoded.request_id, 501);
611 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
612 MerkleCandidateQuoteResponse::Success { candidate_node },
613 ) = decoded.body
614 {
615 assert_eq!(candidate_node, candidate_node_bytes);
616 } else {
617 panic!("expected MerkleCandidateQuoteResponse::Success");
618 }
619 }
620
621 #[test]
622 fn test_merkle_candidate_quote_response_error_encode_decode() {
623 let error = ProtocolError::QuoteFailed("no libp2p keypair".to_string());
624 let response = MerkleCandidateQuoteResponse::Error(error.clone());
625 let msg = ChunkMessage {
626 request_id: 502,
627 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
628 };
629
630 let encoded = msg.encode().expect("encode should succeed");
631 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
632
633 assert_eq!(decoded.request_id, 502);
634 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
635 MerkleCandidateQuoteResponse::Error(err),
636 ) = decoded.body
637 {
638 assert_eq!(err, error);
639 } else {
640 panic!("expected MerkleCandidateQuoteResponse::Error");
641 }
642 }
643}