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 type XorName = [u8; 32];
32
33pub const XORNAME_LEN: usize = std::mem::size_of::<XorName>();
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
40pub enum ChunkMessageBody {
41 PutRequest(ChunkPutRequest),
43 PutResponse(ChunkPutResponse),
45 GetRequest(ChunkGetRequest),
47 GetResponse(ChunkGetResponse),
49 QuoteRequest(ChunkQuoteRequest),
51 QuoteResponse(ChunkQuoteResponse),
53 MerkleCandidateQuoteRequest(MerkleCandidateQuoteRequest),
55 MerkleCandidateQuoteResponse(MerkleCandidateQuoteResponse),
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ChunkMessage {
67 pub request_id: u64,
69 pub body: ChunkMessageBody,
71}
72
73impl ChunkMessage {
74 pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
80 postcard::to_stdvec(self).map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
81 }
82
83 pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
94 if data.len() > MAX_WIRE_MESSAGE_SIZE {
95 return Err(ProtocolError::MessageTooLarge {
96 size: data.len(),
97 max_size: MAX_WIRE_MESSAGE_SIZE,
98 });
99 }
100 postcard::from_bytes(data).map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ChunkPutRequest {
111 pub address: XorName,
113 pub content: Vec<u8>,
115 pub payment_proof: Option<Vec<u8>>,
118}
119
120impl ChunkPutRequest {
121 #[must_use]
123 pub fn new(address: XorName, content: Vec<u8>) -> Self {
124 Self {
125 address,
126 content,
127 payment_proof: None,
128 }
129 }
130
131 #[must_use]
133 pub fn with_payment(address: XorName, content: Vec<u8>, payment_proof: Vec<u8>) -> Self {
134 Self {
135 address,
136 content,
137 payment_proof: Some(payment_proof),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub enum ChunkPutResponse {
145 Success {
147 address: XorName,
149 },
150 AlreadyExists {
152 address: XorName,
154 },
155 PaymentRequired {
157 message: String,
159 },
160 Error(ProtocolError),
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct ChunkGetRequest {
171 pub address: XorName,
173}
174
175impl ChunkGetRequest {
176 #[must_use]
178 pub fn new(address: XorName) -> Self {
179 Self { address }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub enum ChunkGetResponse {
186 Success {
188 address: XorName,
190 content: Vec<u8>,
192 },
193 NotFound {
195 address: XorName,
197 },
198 Error(ProtocolError),
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct ChunkQuoteRequest {
209 pub address: XorName,
211 pub data_size: u64,
213 pub data_type: u32,
215}
216
217impl ChunkQuoteRequest {
218 #[must_use]
220 pub fn new(address: XorName, data_size: u64) -> Self {
221 Self {
222 address,
223 data_size,
224 data_type: DATA_TYPE_CHUNK,
225 }
226 }
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub enum ChunkQuoteResponse {
232 Success {
238 quote: Vec<u8>,
240 already_stored: bool,
242 },
243 Error(ProtocolError),
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct MerkleCandidateQuoteRequest {
257 pub address: XorName,
259 pub data_type: u32,
261 pub data_size: u64,
263 pub merkle_payment_timestamp: u64,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub enum MerkleCandidateQuoteResponse {
270 Success {
273 candidate_node: Vec<u8>,
275 },
276 Error(ProtocolError),
278}
279
280pub const PROOF_TAG_SINGLE_NODE: u8 = 0x01;
287pub const PROOF_TAG_MERKLE: u8 = 0x02;
289
290#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
296pub enum ProtocolError {
297 SerializationFailed(String),
299 DeserializationFailed(String),
301 MessageTooLarge {
303 size: usize,
305 max_size: usize,
307 },
308 ChunkTooLarge {
310 size: usize,
312 max_size: usize,
314 },
315 AddressMismatch {
317 expected: XorName,
319 actual: XorName,
321 },
322 StorageFailed(String),
324 PaymentFailed(String),
326 QuoteFailed(String),
328 Internal(String),
330}
331
332impl std::fmt::Display for ProtocolError {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 match self {
335 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
336 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
337 Self::MessageTooLarge { size, max_size } => {
338 write!(f, "message size {size} exceeds maximum {max_size}")
339 }
340 Self::ChunkTooLarge { size, max_size } => {
341 write!(f, "chunk size {size} exceeds maximum {max_size}")
342 }
343 Self::AddressMismatch { expected, actual } => {
344 write!(
345 f,
346 "address mismatch: expected {}, got {}",
347 hex::encode(expected),
348 hex::encode(actual)
349 )
350 }
351 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
352 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
353 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
354 Self::Internal(msg) => write!(f, "internal error: {msg}"),
355 }
356 }
357}
358
359impl std::error::Error for ProtocolError {}
360
361#[cfg(test)]
362#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_put_request_encode_decode() {
368 let address = [0xAB; 32];
369 let content = vec![1, 2, 3, 4, 5];
370 let request = ChunkPutRequest::new(address, content.clone());
371 let msg = ChunkMessage {
372 request_id: 42,
373 body: ChunkMessageBody::PutRequest(request),
374 };
375
376 let encoded = msg.encode().expect("encode should succeed");
377 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
378
379 assert_eq!(decoded.request_id, 42);
380 if let ChunkMessageBody::PutRequest(req) = decoded.body {
381 assert_eq!(req.address, address);
382 assert_eq!(req.content, content);
383 assert!(req.payment_proof.is_none());
384 } else {
385 panic!("expected PutRequest");
386 }
387 }
388
389 #[test]
390 fn test_put_request_with_payment() {
391 let address = [0xAB; 32];
392 let content = vec![1, 2, 3, 4, 5];
393 let payment = vec![10, 20, 30];
394 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
395
396 assert_eq!(request.address, address);
397 assert_eq!(request.content, content);
398 assert_eq!(request.payment_proof, Some(payment));
399 }
400
401 #[test]
402 fn test_get_request_encode_decode() {
403 let address = [0xCD; 32];
404 let request = ChunkGetRequest::new(address);
405 let msg = ChunkMessage {
406 request_id: 7,
407 body: ChunkMessageBody::GetRequest(request),
408 };
409
410 let encoded = msg.encode().expect("encode should succeed");
411 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
412
413 assert_eq!(decoded.request_id, 7);
414 if let ChunkMessageBody::GetRequest(req) = decoded.body {
415 assert_eq!(req.address, address);
416 } else {
417 panic!("expected GetRequest");
418 }
419 }
420
421 #[test]
422 fn test_put_response_success() {
423 let address = [0xEF; 32];
424 let response = ChunkPutResponse::Success { address };
425 let msg = ChunkMessage {
426 request_id: 99,
427 body: ChunkMessageBody::PutResponse(response),
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, 99);
434 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
435 decoded.body
436 {
437 assert_eq!(addr, address);
438 } else {
439 panic!("expected PutResponse::Success");
440 }
441 }
442
443 #[test]
444 fn test_get_response_not_found() {
445 let address = [0x12; 32];
446 let response = ChunkGetResponse::NotFound { address };
447 let msg = ChunkMessage {
448 request_id: 0,
449 body: ChunkMessageBody::GetResponse(response),
450 };
451
452 let encoded = msg.encode().expect("encode should succeed");
453 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
454
455 assert_eq!(decoded.request_id, 0);
456 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
457 decoded.body
458 {
459 assert_eq!(addr, address);
460 } else {
461 panic!("expected GetResponse::NotFound");
462 }
463 }
464
465 #[test]
466 fn test_quote_request_encode_decode() {
467 let address = [0x34; 32];
468 let request = ChunkQuoteRequest::new(address, 1024);
469 let msg = ChunkMessage {
470 request_id: 1,
471 body: ChunkMessageBody::QuoteRequest(request),
472 };
473
474 let encoded = msg.encode().expect("encode should succeed");
475 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
476
477 assert_eq!(decoded.request_id, 1);
478 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
479 assert_eq!(req.address, address);
480 assert_eq!(req.data_size, 1024);
481 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
482 } else {
483 panic!("expected QuoteRequest");
484 }
485 }
486
487 #[test]
488 fn test_protocol_error_display() {
489 let err = ProtocolError::ChunkTooLarge {
490 size: 5_000_000,
491 max_size: MAX_CHUNK_SIZE,
492 };
493 assert!(err.to_string().contains("5000000"));
494 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
495
496 let err = ProtocolError::AddressMismatch {
497 expected: [0xAA; 32],
498 actual: [0xBB; 32],
499 };
500 let display = err.to_string();
501 assert!(display.contains("address mismatch"));
502 }
503
504 #[test]
505 fn test_decode_rejects_oversized_payload() {
506 let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
507 let result = ChunkMessage::decode(&oversized);
508 assert!(result.is_err());
509 let err = result.unwrap_err();
510 assert!(
511 matches!(err, ProtocolError::MessageTooLarge { .. }),
512 "expected MessageTooLarge, got {err:?}"
513 );
514 }
515
516 #[test]
517 fn test_invalid_decode() {
518 let invalid_data = vec![0xFF, 0xFF, 0xFF];
519 let result = ChunkMessage::decode(&invalid_data);
520 assert!(result.is_err());
521 }
522
523 #[test]
524 fn test_constants() {
525 assert_eq!(CHUNK_PROTOCOL_ID, "autonomi.ant.chunk.v1");
526 assert_eq!(PROTOCOL_VERSION, 1);
527 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
528 assert_eq!(DATA_TYPE_CHUNK, 0);
529 }
530
531 #[test]
532 fn test_proof_tag_constants() {
533 assert_ne!(PROOF_TAG_SINGLE_NODE, PROOF_TAG_MERKLE);
535 assert_ne!(PROOF_TAG_SINGLE_NODE, 0x00);
536 assert_ne!(PROOF_TAG_MERKLE, 0x00);
537 assert_eq!(PROOF_TAG_SINGLE_NODE, 0x01);
538 assert_eq!(PROOF_TAG_MERKLE, 0x02);
539 }
540
541 #[test]
542 fn test_merkle_candidate_quote_request_encode_decode() {
543 let address = [0x56; 32];
544 let request = MerkleCandidateQuoteRequest {
545 address,
546 data_type: DATA_TYPE_CHUNK,
547 data_size: 2048,
548 merkle_payment_timestamp: 1_700_000_000,
549 };
550 let msg = ChunkMessage {
551 request_id: 500,
552 body: ChunkMessageBody::MerkleCandidateQuoteRequest(request),
553 };
554
555 let encoded = msg.encode().expect("encode should succeed");
556 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
557
558 assert_eq!(decoded.request_id, 500);
559 if let ChunkMessageBody::MerkleCandidateQuoteRequest(req) = decoded.body {
560 assert_eq!(req.address, address);
561 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
562 assert_eq!(req.data_size, 2048);
563 assert_eq!(req.merkle_payment_timestamp, 1_700_000_000);
564 } else {
565 panic!("expected MerkleCandidateQuoteRequest");
566 }
567 }
568
569 #[test]
570 fn test_merkle_candidate_quote_response_success_encode_decode() {
571 let candidate_node_bytes = vec![0xAA, 0xBB, 0xCC, 0xDD];
572 let response = MerkleCandidateQuoteResponse::Success {
573 candidate_node: candidate_node_bytes.clone(),
574 };
575 let msg = ChunkMessage {
576 request_id: 501,
577 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
578 };
579
580 let encoded = msg.encode().expect("encode should succeed");
581 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
582
583 assert_eq!(decoded.request_id, 501);
584 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
585 MerkleCandidateQuoteResponse::Success { candidate_node },
586 ) = decoded.body
587 {
588 assert_eq!(candidate_node, candidate_node_bytes);
589 } else {
590 panic!("expected MerkleCandidateQuoteResponse::Success");
591 }
592 }
593
594 #[test]
595 fn test_merkle_candidate_quote_response_error_encode_decode() {
596 let error = ProtocolError::QuoteFailed("no libp2p keypair".to_string());
597 let response = MerkleCandidateQuoteResponse::Error(error.clone());
598 let msg = ChunkMessage {
599 request_id: 502,
600 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
601 };
602
603 let encoded = msg.encode().expect("encode should succeed");
604 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
605
606 assert_eq!(decoded.request_id, 502);
607 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
608 MerkleCandidateQuoteResponse::Error(err),
609 ) = decoded.body
610 {
611 assert_eq!(err, error);
612 } else {
613 panic!("expected MerkleCandidateQuoteResponse::Error");
614 }
615 }
616}