use serde::{Deserialize, Serialize};
pub const CHUNK_PROTOCOL_ID: &str = "autonomi.ant.chunk.v1";
pub const PROTOCOL_VERSION: u16 = 1;
pub const MAX_CHUNK_SIZE: usize = 4 * 1024 * 1024;
pub const MAX_WIRE_MESSAGE_SIZE: usize = 5 * 1024 * 1024;
pub const DATA_TYPE_CHUNK: u32 = 0;
pub type XorName = [u8; 32];
pub const XORNAME_LEN: usize = std::mem::size_of::<XorName>();
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChunkMessageBody {
PutRequest(ChunkPutRequest),
PutResponse(ChunkPutResponse),
GetRequest(ChunkGetRequest),
GetResponse(ChunkGetResponse),
QuoteRequest(ChunkQuoteRequest),
QuoteResponse(ChunkQuoteResponse),
MerkleCandidateQuoteRequest(MerkleCandidateQuoteRequest),
MerkleCandidateQuoteResponse(MerkleCandidateQuoteResponse),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkMessage {
pub request_id: u64,
pub body: ChunkMessageBody,
}
impl ChunkMessage {
pub fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
postcard::to_stdvec(self).map_err(|e| ProtocolError::SerializationFailed(e.to_string()))
}
pub fn decode(data: &[u8]) -> Result<Self, ProtocolError> {
if data.len() > MAX_WIRE_MESSAGE_SIZE {
return Err(ProtocolError::MessageTooLarge {
size: data.len(),
max_size: MAX_WIRE_MESSAGE_SIZE,
});
}
postcard::from_bytes(data).map_err(|e| ProtocolError::DeserializationFailed(e.to_string()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkPutRequest {
pub address: XorName,
pub content: Vec<u8>,
pub payment_proof: Option<Vec<u8>>,
}
impl ChunkPutRequest {
#[must_use]
pub fn new(address: XorName, content: Vec<u8>) -> Self {
Self {
address,
content,
payment_proof: None,
}
}
#[must_use]
pub fn with_payment(address: XorName, content: Vec<u8>, payment_proof: Vec<u8>) -> Self {
Self {
address,
content,
payment_proof: Some(payment_proof),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChunkPutResponse {
Success {
address: XorName,
},
AlreadyExists {
address: XorName,
},
PaymentRequired {
message: String,
},
Error(ProtocolError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkGetRequest {
pub address: XorName,
}
impl ChunkGetRequest {
#[must_use]
pub fn new(address: XorName) -> Self {
Self { address }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChunkGetResponse {
Success {
address: XorName,
content: Vec<u8>,
},
NotFound {
address: XorName,
},
Error(ProtocolError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkQuoteRequest {
pub address: XorName,
pub data_size: u64,
pub data_type: u32,
}
impl ChunkQuoteRequest {
#[must_use]
pub fn new(address: XorName, data_size: u64) -> Self {
Self {
address,
data_size,
data_type: DATA_TYPE_CHUNK,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChunkQuoteResponse {
Success {
quote: Vec<u8>,
already_stored: bool,
},
Error(ProtocolError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MerkleCandidateQuoteRequest {
pub address: XorName,
pub data_type: u32,
pub data_size: u64,
pub merkle_payment_timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MerkleCandidateQuoteResponse {
Success {
candidate_node: Vec<u8>,
},
Error(ProtocolError),
}
pub const PROOF_TAG_SINGLE_NODE: u8 = 0x01;
pub const PROOF_TAG_MERKLE: u8 = 0x02;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProtocolError {
SerializationFailed(String),
DeserializationFailed(String),
MessageTooLarge {
size: usize,
max_size: usize,
},
ChunkTooLarge {
size: usize,
max_size: usize,
},
AddressMismatch {
expected: XorName,
actual: XorName,
},
StorageFailed(String),
PaymentFailed(String),
QuoteFailed(String),
Internal(String),
}
impl std::fmt::Display for ProtocolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SerializationFailed(msg) => write!(f, "serialization failed: {msg}"),
Self::DeserializationFailed(msg) => write!(f, "deserialization failed: {msg}"),
Self::MessageTooLarge { size, max_size } => {
write!(f, "message size {size} exceeds maximum {max_size}")
}
Self::ChunkTooLarge { size, max_size } => {
write!(f, "chunk size {size} exceeds maximum {max_size}")
}
Self::AddressMismatch { expected, actual } => {
write!(
f,
"address mismatch: expected {}, got {}",
hex::encode(expected),
hex::encode(actual)
)
}
Self::StorageFailed(msg) => write!(f, "storage failed: {msg}"),
Self::PaymentFailed(msg) => write!(f, "payment failed: {msg}"),
Self::QuoteFailed(msg) => write!(f, "quote failed: {msg}"),
Self::Internal(msg) => write!(f, "internal error: {msg}"),
}
}
}
impl std::error::Error for ProtocolError {}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn test_put_request_encode_decode() {
let address = [0xAB; 32];
let content = vec![1, 2, 3, 4, 5];
let request = ChunkPutRequest::new(address, content.clone());
let msg = ChunkMessage {
request_id: 42,
body: ChunkMessageBody::PutRequest(request),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 42);
if let ChunkMessageBody::PutRequest(req) = decoded.body {
assert_eq!(req.address, address);
assert_eq!(req.content, content);
assert!(req.payment_proof.is_none());
} else {
panic!("expected PutRequest");
}
}
#[test]
fn test_put_request_with_payment() {
let address = [0xAB; 32];
let content = vec![1, 2, 3, 4, 5];
let payment = vec![10, 20, 30];
let request = ChunkPutRequest::with_payment(address, content.clone(), payment.clone());
assert_eq!(request.address, address);
assert_eq!(request.content, content);
assert_eq!(request.payment_proof, Some(payment));
}
#[test]
fn test_get_request_encode_decode() {
let address = [0xCD; 32];
let request = ChunkGetRequest::new(address);
let msg = ChunkMessage {
request_id: 7,
body: ChunkMessageBody::GetRequest(request),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 7);
if let ChunkMessageBody::GetRequest(req) = decoded.body {
assert_eq!(req.address, address);
} else {
panic!("expected GetRequest");
}
}
#[test]
fn test_put_response_success() {
let address = [0xEF; 32];
let response = ChunkPutResponse::Success { address };
let msg = ChunkMessage {
request_id: 99,
body: ChunkMessageBody::PutResponse(response),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 99);
if let ChunkMessageBody::PutResponse(ChunkPutResponse::Success { address: addr }) =
decoded.body
{
assert_eq!(addr, address);
} else {
panic!("expected PutResponse::Success");
}
}
#[test]
fn test_get_response_not_found() {
let address = [0x12; 32];
let response = ChunkGetResponse::NotFound { address };
let msg = ChunkMessage {
request_id: 0,
body: ChunkMessageBody::GetResponse(response),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 0);
if let ChunkMessageBody::GetResponse(ChunkGetResponse::NotFound { address: addr }) =
decoded.body
{
assert_eq!(addr, address);
} else {
panic!("expected GetResponse::NotFound");
}
}
#[test]
fn test_quote_request_encode_decode() {
let address = [0x34; 32];
let request = ChunkQuoteRequest::new(address, 1024);
let msg = ChunkMessage {
request_id: 1,
body: ChunkMessageBody::QuoteRequest(request),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 1);
if let ChunkMessageBody::QuoteRequest(req) = decoded.body {
assert_eq!(req.address, address);
assert_eq!(req.data_size, 1024);
assert_eq!(req.data_type, DATA_TYPE_CHUNK);
} else {
panic!("expected QuoteRequest");
}
}
#[test]
fn test_protocol_error_display() {
let err = ProtocolError::ChunkTooLarge {
size: 5_000_000,
max_size: MAX_CHUNK_SIZE,
};
assert!(err.to_string().contains("5000000"));
assert!(err.to_string().contains(&MAX_CHUNK_SIZE.to_string()));
let err = ProtocolError::AddressMismatch {
expected: [0xAA; 32],
actual: [0xBB; 32],
};
let display = err.to_string();
assert!(display.contains("address mismatch"));
}
#[test]
fn test_decode_rejects_oversized_payload() {
let oversized = vec![0u8; MAX_WIRE_MESSAGE_SIZE + 1];
let result = ChunkMessage::decode(&oversized);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, ProtocolError::MessageTooLarge { .. }),
"expected MessageTooLarge, got {err:?}"
);
}
#[test]
fn test_invalid_decode() {
let invalid_data = vec![0xFF, 0xFF, 0xFF];
let result = ChunkMessage::decode(&invalid_data);
assert!(result.is_err());
}
#[test]
fn test_constants() {
assert_eq!(CHUNK_PROTOCOL_ID, "autonomi.ant.chunk.v1");
assert_eq!(PROTOCOL_VERSION, 1);
assert_eq!(MAX_CHUNK_SIZE, 4 * 1024 * 1024);
assert_eq!(DATA_TYPE_CHUNK, 0);
}
#[test]
fn test_proof_tag_constants() {
assert_ne!(PROOF_TAG_SINGLE_NODE, PROOF_TAG_MERKLE);
assert_ne!(PROOF_TAG_SINGLE_NODE, 0x00);
assert_ne!(PROOF_TAG_MERKLE, 0x00);
assert_eq!(PROOF_TAG_SINGLE_NODE, 0x01);
assert_eq!(PROOF_TAG_MERKLE, 0x02);
}
#[test]
fn test_merkle_candidate_quote_request_encode_decode() {
let address = [0x56; 32];
let request = MerkleCandidateQuoteRequest {
address,
data_type: DATA_TYPE_CHUNK,
data_size: 2048,
merkle_payment_timestamp: 1_700_000_000,
};
let msg = ChunkMessage {
request_id: 500,
body: ChunkMessageBody::MerkleCandidateQuoteRequest(request),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 500);
if let ChunkMessageBody::MerkleCandidateQuoteRequest(req) = decoded.body {
assert_eq!(req.address, address);
assert_eq!(req.data_type, DATA_TYPE_CHUNK);
assert_eq!(req.data_size, 2048);
assert_eq!(req.merkle_payment_timestamp, 1_700_000_000);
} else {
panic!("expected MerkleCandidateQuoteRequest");
}
}
#[test]
fn test_merkle_candidate_quote_response_success_encode_decode() {
let candidate_node_bytes = vec![0xAA, 0xBB, 0xCC, 0xDD];
let response = MerkleCandidateQuoteResponse::Success {
candidate_node: candidate_node_bytes.clone(),
};
let msg = ChunkMessage {
request_id: 501,
body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 501);
if let ChunkMessageBody::MerkleCandidateQuoteResponse(
MerkleCandidateQuoteResponse::Success { candidate_node },
) = decoded.body
{
assert_eq!(candidate_node, candidate_node_bytes);
} else {
panic!("expected MerkleCandidateQuoteResponse::Success");
}
}
#[test]
fn test_merkle_candidate_quote_response_error_encode_decode() {
let error = ProtocolError::QuoteFailed("no libp2p keypair".to_string());
let response = MerkleCandidateQuoteResponse::Error(error.clone());
let msg = ChunkMessage {
request_id: 502,
body: ChunkMessageBody::MerkleCandidateQuoteResponse(response),
};
let encoded = msg.encode().expect("encode should succeed");
let decoded = ChunkMessage::decode(&encoded).expect("decode should succeed");
assert_eq!(decoded.request_id, 502);
if let ChunkMessageBody::MerkleCandidateQuoteResponse(
MerkleCandidateQuoteResponse::Error(err),
) = decoded.body
{
assert_eq!(err, error);
} else {
panic!("expected MerkleCandidateQuoteResponse::Error");
}
}
}