use crate::Hash;
use serde::{Deserialize, Serialize};
pub const FIBRE_MAGIC: [u8; 4] = [0xF1, 0xB3, 0xE0, 0x00]; pub const FIBRE_VERSION: u8 = 1;
pub const PACKET_TYPE_CHUNK: u8 = 0x01;
pub const PACKET_TYPE_ACK: u8 = 0x02;
pub const PACKET_TYPE_COMPLETE: u8 = 0x03;
pub const PACKET_TYPE_ERROR: u8 = 0x04;
pub const MAX_PACKET_SIZE: usize = 1500; pub const HEADER_SIZE: usize = 62;
pub const MAX_DATA_SIZE: usize = MAX_PACKET_SIZE - HEADER_SIZE - 4; pub const DEFAULT_SHARD_SIZE: usize = 1400;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FecChunk {
pub index: u32,
pub total_chunks: u32,
pub data_chunks: u32,
pub data: Vec<u8>,
pub size: usize,
pub block_hash: Hash,
pub sequence: u64,
pub magic: [u8; 4],
}
impl FecChunk {
pub fn serialize(&self) -> Result<Vec<u8>, FibreProtocolError> {
let mut packet = Vec::with_capacity(HEADER_SIZE + self.data.len() + 4);
packet.extend_from_slice(&FIBRE_MAGIC);
packet.push(FIBRE_VERSION);
packet.push(PACKET_TYPE_CHUNK);
packet.extend_from_slice(&self.block_hash);
packet.extend_from_slice(&self.sequence.to_be_bytes());
packet.extend_from_slice(&self.index.to_be_bytes());
packet.extend_from_slice(&self.total_chunks.to_be_bytes());
packet.extend_from_slice(&self.data_chunks.to_be_bytes());
packet.extend_from_slice(&(self.data.len() as u32).to_be_bytes());
packet.extend_from_slice(&self.data);
let checksum = crc32fast::hash(&packet);
packet.extend_from_slice(&checksum.to_be_bytes());
Ok(packet)
}
pub fn deserialize(data: &[u8]) -> Result<Self, FibreProtocolError> {
if data.len() < HEADER_SIZE + 4 {
return Err(FibreProtocolError::InvalidPacket(
"Packet too short".to_string(),
));
}
if data[0..4] != FIBRE_MAGIC {
return Err(FibreProtocolError::InvalidPacket(
"Invalid magic bytes".to_string(),
));
}
let received_checksum = u32::from_be_bytes([
data[data.len() - 4],
data[data.len() - 3],
data[data.len() - 2],
data[data.len() - 1],
]);
let calculated_checksum = crc32fast::hash(&data[..data.len() - 4]);
if received_checksum != calculated_checksum {
return Err(FibreProtocolError::InvalidPacket(
"Checksum mismatch".to_string(),
));
}
let version = data[4];
if version != FIBRE_VERSION {
return Err(FibreProtocolError::InvalidPacket(format!(
"Unsupported version: {version}"
)));
}
let packet_type = data[5];
if packet_type != PACKET_TYPE_CHUNK {
return Err(FibreProtocolError::InvalidPacket(format!(
"Unexpected packet type: {packet_type}"
)));
}
let block_hash: Hash = data[6..38]
.try_into()
.map_err(|_| FibreProtocolError::InvalidPacket("Invalid block hash".to_string()))?;
let sequence = u64::from_be_bytes(data[38..46].try_into().unwrap());
let index = u32::from_be_bytes(data[46..50].try_into().unwrap());
let total_chunks = u32::from_be_bytes(data[50..54].try_into().unwrap());
let data_chunks = u32::from_be_bytes(data[54..58].try_into().unwrap());
let data_length = u32::from_be_bytes(data[58..62].try_into().unwrap()) as usize;
if data.len() < HEADER_SIZE + data_length + 4 {
return Err(FibreProtocolError::InvalidPacket(
"Packet data length mismatch".to_string(),
));
}
let chunk_data = data[62..62 + data_length].to_vec();
let chunk_size = chunk_data.len();
Ok(FecChunk {
index,
total_chunks,
data_chunks,
data: chunk_data,
size: chunk_size,
block_hash,
sequence,
magic: FIBRE_MAGIC,
})
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct FibreCapabilities {
pub supports_fec: bool,
pub max_chunk_size: usize,
pub min_latency: bool,
}
impl Default for FibreCapabilities {
fn default() -> Self {
Self {
supports_fec: true,
max_chunk_size: DEFAULT_SHARD_SIZE,
min_latency: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FibreConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_fec_parity_ratio")]
pub fec_parity_ratio: f64,
#[serde(default = "default_chunk_timeout")]
pub chunk_timeout_secs: u64,
#[serde(default = "default_max_retries")]
pub max_retries: u32,
#[serde(default = "default_max_assemblies")]
pub max_assemblies: usize,
}
fn default_true() -> bool {
true
}
fn default_fec_parity_ratio() -> f64 {
0.2
}
fn default_chunk_timeout() -> u64 {
2
}
fn default_max_retries() -> u32 {
3
}
fn default_max_assemblies() -> usize {
10
}
impl Default for FibreConfig {
fn default() -> Self {
Self {
enabled: true,
fec_parity_ratio: 0.2,
chunk_timeout_secs: 2,
max_retries: 3,
max_assemblies: 10,
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum FibreProtocolError {
#[error("Invalid packet: {0}")]
InvalidPacket(String),
#[error("Serialization error: {0}")]
SerializationError(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fec_chunk_serialize_deserialize() {
let chunk = FecChunk {
index: 0,
total_chunks: 10,
data_chunks: 8,
data: vec![1, 2, 3, 4, 5],
size: 5,
block_hash: [0x42; 32],
sequence: 12345,
magic: FIBRE_MAGIC,
};
let serialized = chunk.serialize().unwrap();
assert!(serialized.len() >= HEADER_SIZE + 5 + 4);
let deserialized = FecChunk::deserialize(&serialized).unwrap();
assert_eq!(deserialized.index, chunk.index);
assert_eq!(deserialized.total_chunks, chunk.total_chunks);
assert_eq!(deserialized.data_chunks, chunk.data_chunks);
assert_eq!(deserialized.data, chunk.data);
assert_eq!(deserialized.block_hash, chunk.block_hash);
assert_eq!(deserialized.sequence, chunk.sequence);
}
#[test]
fn test_fec_chunk_invalid_magic() {
let mut data = vec![0u8; HEADER_SIZE + 4];
data[0..4].copy_from_slice(&[0xFF; 4]);
let result = FecChunk::deserialize(&data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid magic"));
}
#[test]
fn test_fec_chunk_invalid_checksum() {
let chunk = FecChunk {
index: 0,
total_chunks: 10,
data_chunks: 8,
data: vec![1, 2, 3],
size: 3,
block_hash: [0x42; 32],
sequence: 12345,
magic: FIBRE_MAGIC,
};
let serialized = chunk.serialize().unwrap();
let mut corrupted = serialized.clone();
let last_idx = corrupted.len() - 1;
corrupted[last_idx] ^= 0xFF;
let result = FecChunk::deserialize(&corrupted);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Checksum"));
}
#[test]
fn test_fibre_config_default() {
let config = FibreConfig::default();
assert!(config.enabled);
assert_eq!(config.fec_parity_ratio, 0.2);
assert_eq!(config.chunk_timeout_secs, 2);
assert_eq!(config.max_retries, 3);
assert_eq!(config.max_assemblies, 10);
}
#[test]
fn test_fibre_capabilities_default() {
let caps = FibreCapabilities::default();
assert!(caps.supports_fec);
assert_eq!(caps.max_chunk_size, DEFAULT_SHARD_SIZE);
assert!(caps.min_latency);
}
}