saorsa_node/ant_protocol/
chunk.rs1use bincode::Options;
10use serde::{Deserialize, Serialize};
11
12pub const CHUNK_PROTOCOL_ID: &str = "saorsa/ant/chunk/v1";
14
15pub const PROTOCOL_VERSION: u16 = 1;
17
18pub const MAX_CHUNK_SIZE: usize = 4 * 1024 * 1024;
20
21const MAX_WIRE_MESSAGE_SIZE: u64 = (MAX_CHUNK_SIZE + 1024 * 1024) as u64;
28
29pub const DATA_TYPE_CHUNK: u32 = 0;
31
32pub type XorName = [u8; 32];
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
39pub enum ChunkMessageBody {
40 PutRequest(ChunkPutRequest),
42 PutResponse(ChunkPutResponse),
44 GetRequest(ChunkGetRequest),
46 GetResponse(ChunkGetResponse),
48 QuoteRequest(ChunkQuoteRequest),
50 QuoteResponse(ChunkQuoteResponse),
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ChunkMessage {
62 pub request_id: u64,
64 pub body: ChunkMessageBody,
66}
67
68impl ChunkMessage {
69 pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
75 bincode::DefaultOptions::new()
76 .with_limit(MAX_WIRE_MESSAGE_SIZE)
77 .allow_trailing_bytes()
78 .serialize(self)
79 .map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
80 }
81
82 pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
88 bincode::DefaultOptions::new()
89 .with_limit(MAX_WIRE_MESSAGE_SIZE)
90 .allow_trailing_bytes()
91 .deserialize(data)
92 .map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ChunkPutRequest {
103 pub address: XorName,
105 pub content: Vec<u8>,
107 pub payment_proof: Option<Vec<u8>>,
110}
111
112impl ChunkPutRequest {
113 #[must_use]
115 pub fn new(address: XorName, content: Vec<u8>) -> Self {
116 Self {
117 address,
118 content,
119 payment_proof: None,
120 }
121 }
122
123 #[must_use]
125 pub fn with_payment(address: XorName, content: Vec<u8>, payment_proof: Vec<u8>) -> Self {
126 Self {
127 address,
128 content,
129 payment_proof: Some(payment_proof),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub enum ChunkPutResponse {
137 Success {
139 address: XorName,
141 },
142 AlreadyExists {
144 address: XorName,
146 },
147 PaymentRequired {
149 message: String,
151 },
152 Error(ProtocolError),
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ChunkGetRequest {
163 pub address: XorName,
165}
166
167impl ChunkGetRequest {
168 #[must_use]
170 pub fn new(address: XorName) -> Self {
171 Self { address }
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub enum ChunkGetResponse {
178 Success {
180 address: XorName,
182 content: Vec<u8>,
184 },
185 NotFound {
187 address: XorName,
189 },
190 Error(ProtocolError),
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ChunkQuoteRequest {
201 pub address: XorName,
203 pub data_size: u64,
205 pub data_type: u32,
207}
208
209impl ChunkQuoteRequest {
210 #[must_use]
212 pub fn new(address: XorName, data_size: u64) -> Self {
213 Self {
214 address,
215 data_size,
216 data_type: DATA_TYPE_CHUNK,
217 }
218 }
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub enum ChunkQuoteResponse {
224 Success {
226 quote: Vec<u8>,
228 },
229 Error(ProtocolError),
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
239pub enum ProtocolError {
240 SerializationFailed(String),
242 DeserializationFailed(String),
244 ChunkTooLarge {
246 size: usize,
248 max_size: usize,
250 },
251 AddressMismatch {
253 expected: XorName,
255 actual: XorName,
257 },
258 StorageFailed(String),
260 PaymentFailed(String),
262 QuoteFailed(String),
264 Internal(String),
266}
267
268impl std::fmt::Display for ProtocolError {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 match self {
271 Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
272 Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
273 Self::ChunkTooLarge { size, max_size } => {
274 write!(f, "chunk size {size} exceeds maximum {max_size}")
275 }
276 Self::AddressMismatch { expected, actual } => {
277 write!(
278 f,
279 "address mismatch: expected {}, got {}",
280 hex::encode(expected),
281 hex::encode(actual)
282 )
283 }
284 Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
285 Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
286 Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
287 Self::Internal(msg) => write!(f, "internal error: {msg}"),
288 }
289 }
290}
291
292impl std::error::Error for ProtocolError {}
293
294#[cfg(test)]
295#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_put_request_encode_decode() {
301 let address = [0xAB; 32];
302 let content = vec![1, 2, 3, 4, 5];
303 let request = ChunkPutRequest::new(address, content.clone());
304 let msg = ChunkMessage {
305 request_id: 42,
306 body: ChunkMessageBody::PutRequest(request),
307 };
308
309 let encoded = msg.encode().expect("encode should succeed");
310 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
311
312 assert_eq!(decoded.request_id, 42);
313 if let ChunkMessageBody::PutRequest(req) = decoded.body {
314 assert_eq!(req.address, address);
315 assert_eq!(req.content, content);
316 assert!(req.payment_proof.is_none());
317 } else {
318 panic!("expected PutRequest");
319 }
320 }
321
322 #[test]
323 fn test_put_request_with_payment() {
324 let address = [0xAB; 32];
325 let content = vec![1, 2, 3, 4, 5];
326 let payment = vec![10, 20, 30];
327 let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
328
329 assert_eq!(request.address, address);
330 assert_eq!(request.content, content);
331 assert_eq!(request.payment_proof, Some(payment));
332 }
333
334 #[test]
335 fn test_get_request_encode_decode() {
336 let address = [0xCD; 32];
337 let request = ChunkGetRequest::new(address);
338 let msg = ChunkMessage {
339 request_id: 7,
340 body: ChunkMessageBody::GetRequest(request),
341 };
342
343 let encoded = msg.encode().expect("encode should succeed");
344 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
345
346 assert_eq!(decoded.request_id, 7);
347 if let ChunkMessageBody::GetRequest(req) = decoded.body {
348 assert_eq!(req.address, address);
349 } else {
350 panic!("expected GetRequest");
351 }
352 }
353
354 #[test]
355 fn test_put_response_success() {
356 let address = [0xEF; 32];
357 let response = ChunkPutResponse::Success { address };
358 let msg = ChunkMessage {
359 request_id: 99,
360 body: ChunkMessageBody::PutResponse(response),
361 };
362
363 let encoded = msg.encode().expect("encode should succeed");
364 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
365
366 assert_eq!(decoded.request_id, 99);
367 if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
368 decoded.body
369 {
370 assert_eq!(addr, address);
371 } else {
372 panic!("expected PutResponse::Success");
373 }
374 }
375
376 #[test]
377 fn test_get_response_not_found() {
378 let address = [0x12; 32];
379 let response = ChunkGetResponse::NotFound { address };
380 let msg = ChunkMessage {
381 request_id: 0,
382 body: ChunkMessageBody::GetResponse(response),
383 };
384
385 let encoded = msg.encode().expect("encode should succeed");
386 let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
387
388 assert_eq!(decoded.request_id, 0);
389 if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
390 decoded.body
391 {
392 assert_eq!(addr, address);
393 } else {
394 panic!("expected GetResponse::NotFound");
395 }
396 }
397
398 #[test]
399 fn test_quote_request_encode_decode() {
400 let address = [0x34; 32];
401 let request = ChunkQuoteRequest::new(address, 1024);
402 let msg = ChunkMessage {
403 request_id: 1,
404 body: ChunkMessageBody::QuoteRequest(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, 1);
411 if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
412 assert_eq!(req.address, address);
413 assert_eq!(req.data_size, 1024);
414 assert_eq!(req.data_type, DATA_TYPE_CHUNK);
415 } else {
416 panic!("expected QuoteRequest");
417 }
418 }
419
420 #[test]
421 fn test_protocol_error_display() {
422 let err = ProtocolError::ChunkTooLarge {
423 size: 5_000_000,
424 max_size: MAX_CHUNK_SIZE,
425 };
426 assert!(err.to_string().contains("5000000"));
427 assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
428
429 let err = ProtocolError::AddressMismatch {
430 expected: [0xAA; 32],
431 actual: [0xBB; 32],
432 };
433 let display = err.to_string();
434 assert!(display.contains("address mismatch"));
435 }
436
437 #[test]
438 fn test_invalid_decode() {
439 let invalid_data = vec![0xFF, 0xFF, 0xFF];
440 let result = ChunkMessage::decode(&invalid_data);
441 assert!(result.is_err());
442 }
443
444 #[test]
445 fn test_constants() {
446 assert_eq!(CHUNK_PROTOCOL_ID, "saorsa/ant/chunk/v1");
447 assert_eq!(PROTOCOL_VERSION, 1);
448 assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
449 assert_eq!(DATA_TYPE_CHUNK, 0);
450 }
451}