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
33#[derive(Debug, Clone, Serialize, Deserialize)]
37pub enum ChunkMessageBody {
38 PutRequest(ChunkPutRequest),
40 PutResponse(ChunkPutResponse),
42 GetRequest(ChunkGetRequest),
44 GetResponse(ChunkGetResponse),
46 QuoteRequest(ChunkQuoteRequest),
48 QuoteResponse(ChunkQuoteResponse),
50 MerkleCandidateQuoteRequest(MerkleCandidateQuoteRequest),
52 MerkleCandidateQuoteResponse(MerkleCandidateQuoteResponse),
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ChunkMessage {
64 pub request_id: u64,
66 pub body: ChunkMessageBody,
68}
69
70impl ChunkMessage {
71 pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
77 postcard::to_stdvec(self).map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
78 }
79
80 pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
91 if data.len() > MAX_WIRE_MESSAGE_SIZE {
92 return Err(ProtocolError::MessageTooLarge {
93 size: data.len(),
94 max_size: MAX_WIRE_MESSAGE_SIZE,
95 });
96 }
97 postcard::from_bytes(data).map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ChunkPutRequest {
108 pub address: XorName,
110 pub content: Vec<u8>,
112 pub payment_proof: Option<Vec<u8>>,
115}
116
117impl ChunkPutRequest {
118 #[must_use]
120 pub fn new(address: XorName, content: Vec<u8>) -> Self {
121 Self {
122 address,
123 content,
124 payment_proof: None,
125 }
126 }
127
128 #[must_use]
130 pub fn with_payment(address: XorName, content: Vec<u8>, payment_proof: Vec<u8>) -> Self {
131 Self {
132 address,
133 content,
134 payment_proof: Some(payment_proof),
135 }
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub enum ChunkPutResponse {
142 Success {
144 address: XorName,
146 },
147 AlreadyExists {
149 address: XorName,
151 },
152 PaymentRequired {
154 message: String,
156 },
157 Error(ProtocolError),
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct ChunkGetRequest {
168 pub address: XorName,
170}
171
172impl ChunkGetRequest {
173 #[must_use]
175 pub fn new(address: XorName) -> Self {
176 Self { address }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub enum ChunkGetResponse {
183 Success {
185 address: XorName,
187 content: Vec<u8>,
189 },
190 NotFound {
192 address: XorName,
194 },
195 Error(ProtocolError),
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ChunkQuoteRequest {
206 pub address: XorName,
208 pub data_size: u64,
210 pub data_type: u32,
212}
213
214impl ChunkQuoteRequest {
215 #[must_use]
217 pub fn new(address: XorName, data_size: u64) -> Self {
218 Self {
219 address,
220 data_size,
221 data_type: DATA_TYPE_CHUNK,
222 }
223 }
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub enum ChunkQuoteResponse {
229 Success {
235 quote: Vec<u8>,
237 already_stored: bool,
239 },
240 Error(ProtocolError),
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct MerkleCandidateQuoteRequest {
254 pub address: XorName,
256 pub data_type: u32,
258 pub data_size: u64,
260 pub merkle_payment_timestamp: u64,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub enum MerkleCandidateQuoteResponse {
267 Success {
270 candidate_node: Vec<u8>,
272 },
273 Error(ProtocolError),
275}
276
277pub const PROOF_TAG_SINGLE_NODE: u8 = 0x01;
284pub const PROOF_TAG_MERKLE: u8 = 0x02;
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
293pub enum ProtocolError {
294 SerializationFailed(String),
296 DeserializationFailed(String),
298 MessageTooLarge {
300 size: usize,
302 max_size: usize,
304 },
305 ChunkTooLarge {
307 size: usize,
309 max_size: usize,
311 },
312 AddressMismatch {
314 expected: XorName,
316 actual: XorName,
318 },
319 StorageFailed(String),
321 PaymentFailed(String),
323 QuoteFailed(String),
325 Internal(String),
327}
328
329impl std::fmt::Display for ProtocolError {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 match self {
332 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
333 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
334 Self::MessageTooLarge { size, max_size } => {
335 write!(f, "message size {size} exceeds maximum {max_size}")
336 }
337 Self::ChunkTooLarge { size, max_size } => {
338 write!(f, "chunk size {size} exceeds maximum {max_size}")
339 }
340 Self::AddressMismatch { expected, actual } => {
341 write!(
342 f,
343 "address mismatch: expected {}, got {}",
344 hex::encode(expected),
345 hex::encode(actual)
346 )
347 }
348 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
349 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
350 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
351 Self::Internal(msg) => write!(f, "internal error: {msg}"),
352 }
353 }
354}
355
356impl std::error::Error for ProtocolError {}
357
358#[cfg(test)]
359#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_put_request_encode_decode() {
365 let address = [0xAB; 32];
366 let content = vec![1, 2, 3, 4, 5];
367 let request = ChunkPutRequest::new(address, content.clone());
368 let msg = ChunkMessage {
369 request_id: 42,
370 body: ChunkMessageBody::PutRequest(request),
371 };
372
373 let encoded = msg.encode().expect("encode should succeed");
374 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
375
376 assert_eq!(decoded.request_id, 42);
377 if let ChunkMessageBody::PutRequest(req) = decoded.body {
378 assert_eq!(req.address, address);
379 assert_eq!(req.content, content);
380 assert!(req.payment_proof.is_none());
381 } else {
382 panic!("expected PutRequest");
383 }
384 }
385
386 #[test]
387 fn test_put_request_with_payment() {
388 let address = [0xAB; 32];
389 let content = vec![1, 2, 3, 4, 5];
390 let payment = vec![10, 20, 30];
391 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
392
393 assert_eq!(request.address, address);
394 assert_eq!(request.content, content);
395 assert_eq!(request.payment_proof, Some(payment));
396 }
397
398 #[test]
399 fn test_get_request_encode_decode() {
400 let address = [0xCD; 32];
401 let request = ChunkGetRequest::new(address);
402 let msg = ChunkMessage {
403 request_id: 7,
404 body: ChunkMessageBody::GetRequest(request),
405 };
406
407 let encoded = msg.encode().expect("encode should succeed");
408 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
409
410 assert_eq!(decoded.request_id, 7);
411 if let ChunkMessageBody::GetRequest(req) = decoded.body {
412 assert_eq!(req.address, address);
413 } else {
414 panic!("expected GetRequest");
415 }
416 }
417
418 #[test]
419 fn test_put_response_success() {
420 let address = [0xEF; 32];
421 let response = ChunkPutResponse::Success { address };
422 let msg = ChunkMessage {
423 request_id: 99,
424 body: ChunkMessageBody::PutResponse(response),
425 };
426
427 let encoded = msg.encode().expect("encode should succeed");
428 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
429
430 assert_eq!(decoded.request_id, 99);
431 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
432 decoded.body
433 {
434 assert_eq!(addr, address);
435 } else {
436 panic!("expected PutResponse::Success");
437 }
438 }
439
440 #[test]
441 fn test_get_response_not_found() {
442 let address = [0x12; 32];
443 let response = ChunkGetResponse::NotFound { address };
444 let msg = ChunkMessage {
445 request_id: 0,
446 body: ChunkMessageBody::GetResponse(response),
447 };
448
449 let encoded = msg.encode().expect("encode should succeed");
450 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
451
452 assert_eq!(decoded.request_id, 0);
453 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
454 decoded.body
455 {
456 assert_eq!(addr, address);
457 } else {
458 panic!("expected GetResponse::NotFound");
459 }
460 }
461
462 #[test]
463 fn test_quote_request_encode_decode() {
464 let address = [0x34; 32];
465 let request = ChunkQuoteRequest::new(address, 1024);
466 let msg = ChunkMessage {
467 request_id: 1,
468 body: ChunkMessageBody::QuoteRequest(request),
469 };
470
471 let encoded = msg.encode().expect("encode should succeed");
472 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
473
474 assert_eq!(decoded.request_id, 1);
475 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
476 assert_eq!(req.address, address);
477 assert_eq!(req.data_size, 1024);
478 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
479 } else {
480 panic!("expected QuoteRequest");
481 }
482 }
483
484 #[test]
485 fn test_protocol_error_display() {
486 let err = ProtocolError::ChunkTooLarge {
487 size: 5_000_000,
488 max_size: MAX_CHUNK_SIZE,
489 };
490 assert!(err.to_string().contains("5000000"));
491 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
492
493 let err = ProtocolError::AddressMismatch {
494 expected: [0xAA; 32],
495 actual: [0xBB; 32],
496 };
497 let display = err.to_string();
498 assert!(display.contains("address mismatch"));
499 }
500
501 #[test]
502 fn test_decode_rejects_oversized_payload() {
503 let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
504 let result = ChunkMessage::decode(&oversized);
505 assert!(result.is_err());
506 let err = result.unwrap_err();
507 assert!(
508 matches!(err, ProtocolError::MessageTooLarge { .. }),
509 "expected MessageTooLarge, got {err:?}"
510 );
511 }
512
513 #[test]
514 fn test_invalid_decode() {
515 let invalid_data = vec![0xFF, 0xFF, 0xFF];
516 let result = ChunkMessage::decode(&invalid_data);
517 assert!(result.is_err());
518 }
519
520 #[test]
521 fn test_constants() {
522 assert_eq!(CHUNK_PROTOCOL_ID, "autonomi/ant/chunk/v1");
523 assert_eq!(PROTOCOL_VERSION, 1);
524 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
525 assert_eq!(DATA_TYPE_CHUNK, 0);
526 }
527
528 #[test]
529 fn test_proof_tag_constants() {
530 assert_ne!(PROOF_TAG_SINGLE_NODE, PROOF_TAG_MERKLE);
532 assert_ne!(PROOF_TAG_SINGLE_NODE, 0x00);
533 assert_ne!(PROOF_TAG_MERKLE, 0x00);
534 assert_eq!(PROOF_TAG_SINGLE_NODE, 0x01);
535 assert_eq!(PROOF_TAG_MERKLE, 0x02);
536 }
537
538 #[test]
539 fn test_merkle_candidate_quote_request_encode_decode() {
540 let address = [0x56; 32];
541 let request = MerkleCandidateQuoteRequest {
542 address,
543 data_type: DATA_TYPE_CHUNK,
544 data_size: 2048,
545 merkle_payment_timestamp: 1_700_000_000,
546 };
547 let msg = ChunkMessage {
548 request_id: 500,
549 body: ChunkMessageBody::MerkleCandidateQuoteRequest(request),
550 };
551
552 let encoded = msg.encode().expect("encode should succeed");
553 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
554
555 assert_eq!(decoded.request_id, 500);
556 if let ChunkMessageBody::MerkleCandidateQuoteRequest(req) = decoded.body {
557 assert_eq!(req.address, address);
558 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
559 assert_eq!(req.data_size, 2048);
560 assert_eq!(req.merkle_payment_timestamp, 1_700_000_000);
561 } else {
562 panic!("expected MerkleCandidateQuoteRequest");
563 }
564 }
565
566 #[test]
567 fn test_merkle_candidate_quote_response_success_encode_decode() {
568 let candidate_node_bytes = vec![0xAA, 0xBB, 0xCC, 0xDD];
569 let response = MerkleCandidateQuoteResponse::Success {
570 candidate_node: candidate_node_bytes.clone(),
571 };
572 let msg = ChunkMessage {
573 request_id: 501,
574 body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
575 };
576
577 let encoded = msg.encode().expect("encode should succeed");
578 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
579
580 assert_eq!(decoded.request_id, 501);
581 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
582 MerkleCandidateQuoteResponse::Success { candidate_node },
583 ) = decoded.body
584 {
585 assert_eq!(candidate_node, candidate_node_bytes);
586 } else {
587 panic!("expected MerkleCandidateQuoteResponse::Success");
588 }
589 }
590
591 #[test]
592 fn test_merkle_candidate_quote_response_error_encode_decode() {
593 let error = ProtocolError::QuoteFailed("no libp2p keypair".to_string());
594 let response = MerkleCandidateQuoteResponse::Error(error.clone());
595 let msg = ChunkMessage {
596 request_id: 502,
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, 502);
604 if let ChunkMessageBody::MerkleCandidateQuoteResponse(
605 MerkleCandidateQuoteResponse::Error(err),
606 ) = decoded.body
607 {
608 assert_eq!(err, error);
609 } else {
610 panic!("expected MerkleCandidateQuoteResponse::Error");
611 }
612 }
613}