1use serde::{Deserialize, Serialize};
10
11pub const CHUNK_PROTOCOL_ID: &str = "autonomi.ant.chunk.v1";
13
14pub const PROTOCOL_VERSION: u16 = 1;
16
17pub const MAX_CHUNK_SIZE: usize = 4 * 1024 * 1024;
19
20pub const MAX_WIRE_MESSAGE_SIZE: usize = 5 * 1024 * 1024;
26
27pub const DATA_TYPE_CHUNK: u32 = 0;
29
30pub const CLOSE_GROUP_SIZE: usize = 7;
35
36pub const CLOSE_GROUP_MAJORITY: usize = (CLOSE_GROUP_SIZE / 2) + 1;
40
41pub type XorName = [u8; 32];
43
44pub const XORNAME_LEN: usize = std::mem::size_of::<XorName>();
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
54#[non_exhaustive]
55pub enum ChunkMessageBody {
56 PutRequest(ChunkPutRequest),
58 PutResponse(ChunkPutResponse),
60 GetRequest(ChunkGetRequest),
62 GetResponse(ChunkGetResponse),
64 QuoteRequest(ChunkQuoteRequest),
66 QuoteResponse(ChunkQuoteResponse),
68 MerkleCandidateQuoteRequest(MerkleCandidateQuoteRequest),
70 MerkleCandidateQuoteResponse(MerkleCandidateQuoteResponse),
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ChunkMessage {
82 pub request_id: u64,
84 pub body: ChunkMessageBody,
86}
87
88impl ChunkMessage {
89 pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
95 postcard::to_stdvec(self).map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
96 }
97
98 pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
109 if data.len() > MAX_WIRE_MESSAGE_SIZE {
110 return Err(ProtocolError::MessageTooLarge {
111 size: data.len(),
112 max_size: MAX_WIRE_MESSAGE_SIZE,
113 });
114 }
115 postcard::from_bytes(data).map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
116 }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ChunkPutRequest {
126 pub address: XorName,
128 pub content: Vec<u8>,
130 pub payment_proof: Option<Vec<u8>>,
133}
134
135impl ChunkPutRequest {
136 #[must_use]
138 pub fn new(address: XorName, content: Vec<u8>) -> Self {
139 Self {
140 address,
141 content,
142 payment_proof: None,
143 }
144 }
145
146 #[must_use]
148 pub fn with_payment(address: XorName, content: Vec<u8>, payment_proof: Vec<u8>) -> Self {
149 Self {
150 address,
151 content,
152 payment_proof: Some(payment_proof),
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159#[non_exhaustive]
160pub enum ChunkPutResponse {
161 Success {
163 address: XorName,
165 },
166 AlreadyExists {
168 address: XorName,
170 },
171 PaymentRequired {
173 message: String,
175 },
176 Error(ProtocolError),
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct ChunkGetRequest {
187 pub address: XorName,
189}
190
191impl ChunkGetRequest {
192 #[must_use]
194 pub fn new(address: XorName) -> Self {
195 Self { address }
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[non_exhaustive]
202pub enum ChunkGetResponse {
203 Success {
205 address: XorName,
207 content: Vec<u8>,
209 },
210 NotFound {
212 address: XorName,
214 },
215 Error(ProtocolError),
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct ChunkQuoteRequest {
226 pub address: XorName,
228 pub data_size: u64,
230 pub data_type: u32,
232}
233
234impl ChunkQuoteRequest {
235 #[must_use]
237 pub fn new(address: XorName, data_size: u64) -> Self {
238 Self {
239 address,
240 data_size,
241 data_type: DATA_TYPE_CHUNK,
242 }
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248#[non_exhaustive]
249pub enum ChunkQuoteResponse {
250 Success {
256 quote: Vec<u8>,
258 already_stored: bool,
260 },
261 Error(ProtocolError),
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct MerkleCandidateQuoteRequest {
275 pub address: XorName,
277 pub data_type: u32,
279 pub data_size: u64,
281 pub merkle_payment_timestamp: u64,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287#[non_exhaustive]
288pub enum MerkleCandidateQuoteResponse {
289 Success {
292 candidate_node: Vec<u8>,
294 },
295 Error(ProtocolError),
297}
298
299pub const PROOF_TAG_SINGLE_NODE: u8 = 0x01;
306pub const PROOF_TAG_MERKLE: u8 = 0x02;
308
309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
315#[non_exhaustive]
316pub enum ProtocolError {
317 SerializationFailed(String),
319 DeserializationFailed(String),
321 MessageTooLarge {
323 size: usize,
325 max_size: usize,
327 },
328 ChunkTooLarge {
330 size: usize,
332 max_size: usize,
334 },
335 AddressMismatch {
337 expected: XorName,
339 actual: XorName,
341 },
342 StorageFailed(String),
344 PaymentFailed(String),
346 QuoteFailed(String),
348 Internal(String),
350}
351
352impl std::fmt::Display for ProtocolError {
353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354 match self {
355 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
356 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
357 Self::MessageTooLarge { size, max_size } => {
358 write!(f, "message size {size} exceeds maximum {max_size}")
359 }
360 Self::ChunkTooLarge { size, max_size } => {
361 write!(f, "chunk size {size} exceeds maximum {max_size}")
362 }
363 Self::AddressMismatch { expected, actual } => {
364 write!(
365 f,
366 "address mismatch: expected {}, got {}",
367 hex::encode(expected),
368 hex::encode(actual)
369 )
370 }
371 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
372 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
373 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
374 Self::Internal(msg) => write!(f, "internal error: {msg}"),
375 }
376 }
377}
378
379impl std::error::Error for ProtocolError {}
380
381#[cfg(test)]
382#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn test_put_request_encode_decode() {
388 let address = [0xAB; 32];
389 let content = vec![1, 2, 3, 4, 5];
390 let request = ChunkPutRequest::new(address, content.clone());
391 let msg = ChunkMessage {
392 request_id: 42,
393 body: ChunkMessageBody::PutRequest(request),
394 };
395
396 let encoded = msg.encode().expect("encode should succeed");
397 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
398
399 assert_eq!(decoded.request_id, 42);
400 if let ChunkMessageBody::PutRequest(req) = decoded.body {
401 assert_eq!(req.address, address);
402 assert_eq!(req.content, content);
403 assert!(req.payment_proof.is_none());
404 } else {
405 panic!("expected PutRequest");
406 }
407 }
408
409 #[test]
410 fn test_put_request_with_payment() {
411 let address = [0xAB; 32];
412 let content = vec![1, 2, 3, 4, 5];
413 let payment = vec![10, 20, 30];
414 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
415
416 assert_eq!(request.address, address);
417 assert_eq!(request.content, content);
418 assert_eq!(request.payment_proof, Some(payment));
419 }
420
421 #[test]
422 fn test_get_request_encode_decode() {
423 let address = [0xCD; 32];
424 let request = ChunkGetRequest::new(address);
425 let msg = ChunkMessage {
426 request_id: 7,
427 body: ChunkMessageBody::GetRequest(request),
428 };
429
430 let encoded = msg.encode().expect("encode should succeed");
431 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
432
433 assert_eq!(decoded.request_id, 7);
434 if let ChunkMessageBody::GetRequest(req) = decoded.body {
435 assert_eq!(req.address, address);
436 } else {
437 panic!("expected GetRequest");
438 }
439 }
440
441 #[test]
442 fn test_put_response_success() {
443 let address = [0xEF; 32];
444 let response = ChunkPutResponse::Success { address };
445 let msg = ChunkMessage {
446 request_id: 99,
447 body: ChunkMessageBody::PutResponse(response),
448 };
449
450 let encoded = msg.encode().expect("encode should succeed");
451 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
452
453 assert_eq!(decoded.request_id, 99);
454 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
455 decoded.body
456 {
457 assert_eq!(addr, address);
458 } else {
459 panic!("expected PutResponse::Success");
460 }
461 }
462
463 #[test]
464 fn test_get_response_not_found() {
465 let address = [0x12; 32];
466 let response = ChunkGetResponse::NotFound { address };
467 let msg = ChunkMessage {
468 request_id: 0,
469 body: ChunkMessageBody::GetResponse(response),
470 };
471
472 let encoded = msg.encode().expect("encode should succeed");
473 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
474
475 assert_eq!(decoded.request_id, 0);
476 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
477 decoded.body
478 {
479 assert_eq!(addr, address);
480 } else {
481 panic!("expected GetResponse::NotFound");
482 }
483 }
484
485 #[test]
486 fn test_quote_request_encode_decode() {
487 let address = [0x34; 32];
488 let request = ChunkQuoteRequest::new(address, 1024);
489 let msg = ChunkMessage {
490 request_id: 1,
491 body: ChunkMessageBody::QuoteRequest(request),
492 };
493
494 let encoded = msg.encode().expect("encode should succeed");
495 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
496
497 assert_eq!(decoded.request_id, 1);
498 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
499 assert_eq!(req.address, address);
500 assert_eq!(req.data_size, 1024);
501 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
502 } else {
503 panic!("expected QuoteRequest");
504 }
505 }
506
507 #[test]
508 fn test_protocol_error_display() {
509 let err = ProtocolError::ChunkTooLarge {
510 size: 5_000_000,
511 max_size: MAX_CHUNK_SIZE,
512 };
513 assert!(err.to_string().contains("5000000"));
514 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
515
516 let err = ProtocolError::AddressMismatch {
517 expected: [0xAA; 32],
518 actual: [0xBB; 32],
519 };
520 let display = err.to_string();
521 assert!(display.contains("address mismatch"));
522 }
523
524 #[test]
525 fn test_decode_rejects_oversized_payload() {
526 let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
527 let result = ChunkMessage::decode(&oversized);
528 assert!(result.is_err());
529 let err = result.unwrap_err();
530 assert!(
531 matches!(err, ProtocolError::MessageTooLarge { .. }),
532 "expected MessageTooLarge, got {err:?}"
533 );
534 }
535
536 #[test]
537 fn test_invalid_decode() {
538 let invalid_data = vec![0xFF, 0xFF, 0xFF];
539 let result = ChunkMessage::decode(&invalid_data);
540 assert!(result.is_err());
541 }
542
543 #[test]
544 fn test_constants() {
545 assert_eq!(CHUNK_PROTOCOL_ID, "autonomi.ant.chunk.v1");
546 assert_eq!(PROTOCOL_VERSION, 1);
547 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
548 assert_eq!(DATA_TYPE_CHUNK, 0);
549 }
550
551 #[test]
552 fn test_proof_tag_constants() {
553 assert_ne!(PROOF_TAG_SINGLE_NODE, PROOF_TAG_MERKLE);
555 assert_ne!(PROOF_TAG_SINGLE_NODE, 0x00);
556 assert_ne!(PROOF_TAG_MERKLE, 0x00);
557 assert_eq!(PROOF_TAG_SINGLE_NODE, 0x01);
558 assert_eq!(PROOF_TAG_MERKLE, 0x02);
559 }
560
561 #[test]
562 fn test_merkle_candidate_quote_request_encode_decode() {
563 let address = [0x56; 32];
564 let request = MerkleCandidateQuoteRequest {
565 address,
566 data_type: DATA_TYPE_CHUNK,
567 data_size: 2048,
568 merkle_payment_timestamp: 1_700_000_000,
569 };
570 let msg = ChunkMessage {
571 request_id: 500,
572 body: ChunkMessageBody::MerkleCandidateQuoteRequest(request),
573 };
574
575 let encoded = msg.encode().expect("encode should succeed");
576 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
577
578 assert_eq!(decoded.request_id, 500);
579 if let ChunkMessageBody::MerkleCandidateQuoteRequest(req) = decoded.body {
580 assert_eq!(req.address, address);
581 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
582 assert_eq!(req.data_size, 2048);
583 assert_eq!(req.merkle_payment_timestamp, 1_700_000_000);
584 } else {
585 panic!("expected MerkleCandidateQuoteRequest");
586 }
587 }
588
589 #[test]
590 fn test_merkle_candidate_quote_response_success_encode_decode() {
591 let candidate_node_bytes = vec![0xAA, 0xBB, 0xCC, 0xDD];
592 let response = MerkleCandidateQuoteResponse::Success {
593 candidate_node: candidate_node_bytes.clone(),
594 };
595 let msg = ChunkMessage {
596 request_id: 501,
597 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
598 };
599
600 let encoded = msg.encode().expect("encode should succeed");
601 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
602
603 assert_eq!(decoded.request_id, 501);
604 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
605 MerkleCandidateQuoteResponse::Success { candidate_node },
606 ) = decoded.body
607 {
608 assert_eq!(candidate_node, candidate_node_bytes);
609 } else {
610 panic!("expected MerkleCandidateQuoteResponse::Success");
611 }
612 }
613
614 #[test]
615 fn test_merkle_candidate_quote_response_error_encode_decode() {
616 let error = ProtocolError::QuoteFailed("no libp2p keypair".to_string());
617 let response = MerkleCandidateQuoteResponse::Error(error.clone());
618 let msg = ChunkMessage {
619 request_id: 502,
620 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
621 };
622
623 let encoded = msg.encode().expect("encode should succeed");
624 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
625
626 assert_eq!(decoded.request_id, 502);
627 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
628 MerkleCandidateQuoteResponse::Error(err),
629 ) = decoded.body
630 {
631 assert_eq!(err, error);
632 } else {
633 panic!("expected MerkleCandidateQuoteResponse::Error");
634 }
635 }
636}