1use serde::{Deserialize, Serialize};
10
11pub const CHUNK_PROTOCOL_ID: &str = "saorsa/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}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ChunkMessage {
60 pub request_id: u64,
62 pub body: ChunkMessageBody,
64}
65
66impl ChunkMessage {
67 pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
73 postcard::to_stdvec(self).map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
74 }
75
76 pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
87 if data.len() > MAX_WIRE_MESSAGE_SIZE {
88 return Err(ProtocolError::MessageTooLarge {
89 size: data.len(),
90 max_size: MAX_WIRE_MESSAGE_SIZE,
91 });
92 }
93 postcard::from_bytes(data).map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ChunkPutRequest {
104 pub address: XorName,
106 pub content: Vec<u8>,
108 pub payment_proof: Option<Vec<u8>>,
111}
112
113impl ChunkPutRequest {
114 #[must_use]
116 pub fn new(address: XorName, content: Vec<u8>) -> Self {
117 Self {
118 address,
119 content,
120 payment_proof: None,
121 }
122 }
123
124 #[must_use]
126 pub fn with_payment(address: XorName, content: Vec<u8>, payment_proof: Vec<u8>) -> Self {
127 Self {
128 address,
129 content,
130 payment_proof: Some(payment_proof),
131 }
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub enum ChunkPutResponse {
138 Success {
140 address: XorName,
142 },
143 AlreadyExists {
145 address: XorName,
147 },
148 PaymentRequired {
150 message: String,
152 },
153 Error(ProtocolError),
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct ChunkGetRequest {
164 pub address: XorName,
166}
167
168impl ChunkGetRequest {
169 #[must_use]
171 pub fn new(address: XorName) -> Self {
172 Self { address }
173 }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub enum ChunkGetResponse {
179 Success {
181 address: XorName,
183 content: Vec<u8>,
185 },
186 NotFound {
188 address: XorName,
190 },
191 Error(ProtocolError),
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ChunkQuoteRequest {
202 pub address: XorName,
204 pub data_size: u64,
206 pub data_type: u32,
208}
209
210impl ChunkQuoteRequest {
211 #[must_use]
213 pub fn new(address: XorName, data_size: u64) -> Self {
214 Self {
215 address,
216 data_size,
217 data_type: DATA_TYPE_CHUNK,
218 }
219 }
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub enum ChunkQuoteResponse {
225 Success {
231 quote: Vec<u8>,
233 already_stored: bool,
235 },
236 Error(ProtocolError),
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
246pub enum ProtocolError {
247 SerializationFailed(String),
249 DeserializationFailed(String),
251 MessageTooLarge {
253 size: usize,
255 max_size: usize,
257 },
258 ChunkTooLarge {
260 size: usize,
262 max_size: usize,
264 },
265 AddressMismatch {
267 expected: XorName,
269 actual: XorName,
271 },
272 StorageFailed(String),
274 PaymentFailed(String),
276 QuoteFailed(String),
278 Internal(String),
280}
281
282impl std::fmt::Display for ProtocolError {
283 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284 match self {
285 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
286 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
287 Self::MessageTooLarge { size, max_size } => {
288 write!(f, "message size {size} exceeds maximum {max_size}")
289 }
290 Self::ChunkTooLarge { size, max_size } => {
291 write!(f, "chunk size {size} exceeds maximum {max_size}")
292 }
293 Self::AddressMismatch { expected, actual } => {
294 write!(
295 f,
296 "address mismatch: expected {}, got {}",
297 hex::encode(expected),
298 hex::encode(actual)
299 )
300 }
301 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
302 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
303 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
304 Self::Internal(msg) => write!(f, "internal error: {msg}"),
305 }
306 }
307}
308
309impl std::error::Error for ProtocolError {}
310
311#[cfg(test)]
312#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_put_request_encode_decode() {
318 let address = [0xAB; 32];
319 let content = vec![1, 2, 3, 4, 5];
320 let request = ChunkPutRequest::new(address, content.clone());
321 let msg = ChunkMessage {
322 request_id: 42,
323 body: ChunkMessageBody::PutRequest(request),
324 };
325
326 let encoded = msg.encode().expect("encode should succeed");
327 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
328
329 assert_eq!(decoded.request_id, 42);
330 if let ChunkMessageBody::PutRequest(req) = decoded.body {
331 assert_eq!(req.address, address);
332 assert_eq!(req.content, content);
333 assert!(req.payment_proof.is_none());
334 } else {
335 panic!("expected PutRequest");
336 }
337 }
338
339 #[test]
340 fn test_put_request_with_payment() {
341 let address = [0xAB; 32];
342 let content = vec![1, 2, 3, 4, 5];
343 let payment = vec![10, 20, 30];
344 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
345
346 assert_eq!(request.address, address);
347 assert_eq!(request.content, content);
348 assert_eq!(request.payment_proof, Some(payment));
349 }
350
351 #[test]
352 fn test_get_request_encode_decode() {
353 let address = [0xCD; 32];
354 let request = ChunkGetRequest::new(address);
355 let msg = ChunkMessage {
356 request_id: 7,
357 body: ChunkMessageBody::GetRequest(request),
358 };
359
360 let encoded = msg.encode().expect("encode should succeed");
361 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
362
363 assert_eq!(decoded.request_id, 7);
364 if let ChunkMessageBody::GetRequest(req) = decoded.body {
365 assert_eq!(req.address, address);
366 } else {
367 panic!("expected GetRequest");
368 }
369 }
370
371 #[test]
372 fn test_put_response_success() {
373 let address = [0xEF; 32];
374 let response = ChunkPutResponse::Success { address };
375 let msg = ChunkMessage {
376 request_id: 99,
377 body: ChunkMessageBody::PutResponse(response),
378 };
379
380 let encoded = msg.encode().expect("encode should succeed");
381 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
382
383 assert_eq!(decoded.request_id, 99);
384 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
385 decoded.body
386 {
387 assert_eq!(addr, address);
388 } else {
389 panic!("expected PutResponse::Success");
390 }
391 }
392
393 #[test]
394 fn test_get_response_not_found() {
395 let address = [0x12; 32];
396 let response = ChunkGetResponse::NotFound { address };
397 let msg = ChunkMessage {
398 request_id: 0,
399 body: ChunkMessageBody::GetResponse(response),
400 };
401
402 let encoded = msg.encode().expect("encode should succeed");
403 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
404
405 assert_eq!(decoded.request_id, 0);
406 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
407 decoded.body
408 {
409 assert_eq!(addr, address);
410 } else {
411 panic!("expected GetResponse::NotFound");
412 }
413 }
414
415 #[test]
416 fn test_quote_request_encode_decode() {
417 let address = [0x34; 32];
418 let request = ChunkQuoteRequest::new(address, 1024);
419 let msg = ChunkMessage {
420 request_id: 1,
421 body: ChunkMessageBody::QuoteRequest(request),
422 };
423
424 let encoded = msg.encode().expect("encode should succeed");
425 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
426
427 assert_eq!(decoded.request_id, 1);
428 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
429 assert_eq!(req.address, address);
430 assert_eq!(req.data_size, 1024);
431 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
432 } else {
433 panic!("expected QuoteRequest");
434 }
435 }
436
437 #[test]
438 fn test_protocol_error_display() {
439 let err = ProtocolError::ChunkTooLarge {
440 size: 5_000_000,
441 max_size: MAX_CHUNK_SIZE,
442 };
443 assert!(err.to_string().contains("5000000"));
444 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
445
446 let err = ProtocolError::AddressMismatch {
447 expected: [0xAA; 32],
448 actual: [0xBB; 32],
449 };
450 let display = err.to_string();
451 assert!(display.contains("address mismatch"));
452 }
453
454 #[test]
455 fn test_decode_rejects_oversized_payload() {
456 let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
457 let result = ChunkMessage::decode(&oversized);
458 assert!(result.is_err());
459 let err = result.unwrap_err();
460 assert!(
461 matches!(err, ProtocolError::MessageTooLarge { .. }),
462 "expected MessageTooLarge, got {err:?}"
463 );
464 }
465
466 #[test]
467 fn test_invalid_decode() {
468 let invalid_data = vec![0xFF, 0xFF, 0xFF];
469 let result = ChunkMessage::decode(&invalid_data);
470 assert!(result.is_err());
471 }
472
473 #[test]
474 fn test_constants() {
475 assert_eq!(CHUNK_PROTOCOL_ID, "saorsa/ant/chunk/v1");
476 assert_eq!(PROTOCOL_VERSION, 1);
477 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
478 assert_eq!(DATA_TYPE_CHUNK, 0);
479 }
480}