1use crate::Hash;
7use serde::{Deserialize, Serialize};
8
9pub const FIBRE_MAGIC: [u8; 4] = [0xF1, 0xB3, 0xE0, 0x00]; pub const FIBRE_VERSION: u8 = 1;
12pub const PACKET_TYPE_CHUNK: u8 = 0x01;
13pub const PACKET_TYPE_ACK: u8 = 0x02;
14pub const PACKET_TYPE_COMPLETE: u8 = 0x03;
15pub const PACKET_TYPE_ERROR: u8 = 0x04;
16pub const MAX_PACKET_SIZE: usize = 1500; pub const HEADER_SIZE: usize = 62;
18pub const MAX_DATA_SIZE: usize = MAX_PACKET_SIZE - HEADER_SIZE - 4; pub const DEFAULT_SHARD_SIZE: usize = 1400; #[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct FecChunk {
24 pub index: u32,
26 pub total_chunks: u32,
28 pub data_chunks: u32,
30 pub data: Vec<u8>,
32 pub size: usize,
34 pub block_hash: Hash,
36 pub sequence: u64,
38 pub magic: [u8; 4],
40}
41
42impl FecChunk {
43 pub fn serialize(&self) -> Result<Vec<u8>, FibreProtocolError> {
45 let mut packet = Vec::with_capacity(HEADER_SIZE + self.data.len() + 4);
46
47 packet.extend_from_slice(&FIBRE_MAGIC);
49 packet.push(FIBRE_VERSION);
51 packet.push(PACKET_TYPE_CHUNK);
53 packet.extend_from_slice(&self.block_hash);
55 packet.extend_from_slice(&self.sequence.to_be_bytes());
57 packet.extend_from_slice(&self.index.to_be_bytes());
59 packet.extend_from_slice(&self.total_chunks.to_be_bytes());
61 packet.extend_from_slice(&self.data_chunks.to_be_bytes());
63 packet.extend_from_slice(&(self.data.len() as u32).to_be_bytes());
65 packet.extend_from_slice(&self.data);
67
68 let checksum = crc32fast::hash(&packet);
70 packet.extend_from_slice(&checksum.to_be_bytes());
71
72 Ok(packet)
73 }
74
75 pub fn deserialize(data: &[u8]) -> Result<Self, FibreProtocolError> {
77 if data.len() < HEADER_SIZE + 4 {
79 return Err(FibreProtocolError::InvalidPacket(
80 "Packet too short".to_string(),
81 ));
82 }
83
84 if data[0..4] != FIBRE_MAGIC {
86 return Err(FibreProtocolError::InvalidPacket(
87 "Invalid magic bytes".to_string(),
88 ));
89 }
90
91 let received_checksum = u32::from_be_bytes([
93 data[data.len() - 4],
94 data[data.len() - 3],
95 data[data.len() - 2],
96 data[data.len() - 1],
97 ]);
98 let calculated_checksum = crc32fast::hash(&data[..data.len() - 4]);
99 if received_checksum != calculated_checksum {
100 return Err(FibreProtocolError::InvalidPacket(
101 "Checksum mismatch".to_string(),
102 ));
103 }
104
105 let version = data[4];
107 if version != FIBRE_VERSION {
108 return Err(FibreProtocolError::InvalidPacket(format!(
109 "Unsupported version: {version}"
110 )));
111 }
112
113 let packet_type = data[5];
114 if packet_type != PACKET_TYPE_CHUNK {
115 return Err(FibreProtocolError::InvalidPacket(format!(
116 "Unexpected packet type: {packet_type}"
117 )));
118 }
119
120 let block_hash: Hash = data[6..38]
121 .try_into()
122 .map_err(|_| FibreProtocolError::InvalidPacket("Invalid block hash".to_string()))?;
123 let sequence = u64::from_be_bytes(data[38..46].try_into().unwrap());
124 let index = u32::from_be_bytes(data[46..50].try_into().unwrap());
125 let total_chunks = u32::from_be_bytes(data[50..54].try_into().unwrap());
126 let data_chunks = u32::from_be_bytes(data[54..58].try_into().unwrap());
127 let data_length = u32::from_be_bytes(data[58..62].try_into().unwrap()) as usize;
128
129 if data.len() < HEADER_SIZE + data_length + 4 {
131 return Err(FibreProtocolError::InvalidPacket(
132 "Packet data length mismatch".to_string(),
133 ));
134 }
135
136 let chunk_data = data[62..62 + data_length].to_vec();
138 let chunk_size = chunk_data.len();
139
140 Ok(FecChunk {
141 index,
142 total_chunks,
143 data_chunks,
144 data: chunk_data,
145 size: chunk_size,
146 block_hash,
147 sequence,
148 magic: FIBRE_MAGIC,
149 })
150 }
151}
152
153#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
155pub struct FibreCapabilities {
156 pub supports_fec: bool,
158 pub max_chunk_size: usize,
160 pub min_latency: bool,
162}
163
164impl Default for FibreCapabilities {
165 fn default() -> Self {
166 Self {
167 supports_fec: true,
168 max_chunk_size: DEFAULT_SHARD_SIZE,
169 min_latency: true,
170 }
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct FibreConfig {
177 #[serde(default = "default_true")]
179 pub enabled: bool,
180 #[serde(default = "default_fec_parity_ratio")]
182 pub fec_parity_ratio: f64,
183 #[serde(default = "default_chunk_timeout")]
185 pub chunk_timeout_secs: u64,
186 #[serde(default = "default_max_retries")]
188 pub max_retries: u32,
189 #[serde(default = "default_max_assemblies")]
191 pub max_assemblies: usize,
192}
193
194fn default_true() -> bool {
195 true
196}
197fn default_fec_parity_ratio() -> f64 {
198 0.2
199}
200fn default_chunk_timeout() -> u64 {
201 2
202}
203fn default_max_retries() -> u32 {
204 3
205}
206fn default_max_assemblies() -> usize {
207 10
208}
209
210impl Default for FibreConfig {
211 fn default() -> Self {
212 Self {
213 enabled: true,
214 fec_parity_ratio: 0.2,
215 chunk_timeout_secs: 2,
216 max_retries: 3,
217 max_assemblies: 10,
218 }
219 }
220}
221
222#[derive(Debug, Clone, thiserror::Error)]
224pub enum FibreProtocolError {
225 #[error("Invalid packet: {0}")]
226 InvalidPacket(String),
227
228 #[error("Serialization error: {0}")]
229 SerializationError(String),
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_fec_chunk_serialize_deserialize() {
238 let chunk = FecChunk {
239 index: 0,
240 total_chunks: 10,
241 data_chunks: 8,
242 data: vec![1, 2, 3, 4, 5],
243 size: 5,
244 block_hash: [0x42; 32],
245 sequence: 12345,
246 magic: FIBRE_MAGIC,
247 };
248
249 let serialized = chunk.serialize().unwrap();
250 assert!(serialized.len() >= HEADER_SIZE + 5 + 4);
251
252 let deserialized = FecChunk::deserialize(&serialized).unwrap();
253 assert_eq!(deserialized.index, chunk.index);
254 assert_eq!(deserialized.total_chunks, chunk.total_chunks);
255 assert_eq!(deserialized.data_chunks, chunk.data_chunks);
256 assert_eq!(deserialized.data, chunk.data);
257 assert_eq!(deserialized.block_hash, chunk.block_hash);
258 assert_eq!(deserialized.sequence, chunk.sequence);
259 }
260
261 #[test]
262 fn test_fec_chunk_invalid_magic() {
263 let mut data = vec![0u8; HEADER_SIZE + 4];
264 data[0..4].copy_from_slice(&[0xFF; 4]); let result = FecChunk::deserialize(&data);
267 assert!(result.is_err());
268 assert!(result.unwrap_err().to_string().contains("Invalid magic"));
269 }
270
271 #[test]
272 fn test_fec_chunk_invalid_checksum() {
273 let chunk = FecChunk {
274 index: 0,
275 total_chunks: 10,
276 data_chunks: 8,
277 data: vec![1, 2, 3],
278 size: 3,
279 block_hash: [0x42; 32],
280 sequence: 12345,
281 magic: FIBRE_MAGIC,
282 };
283
284 let serialized = chunk.serialize().unwrap();
285 let mut corrupted = serialized.clone();
286 let last_idx = corrupted.len() - 1;
288 corrupted[last_idx] ^= 0xFF;
289
290 let result = FecChunk::deserialize(&corrupted);
291 assert!(result.is_err());
292 assert!(result.unwrap_err().to_string().contains("Checksum"));
293 }
294
295 #[test]
296 fn test_fibre_config_default() {
297 let config = FibreConfig::default();
298 assert!(config.enabled);
299 assert_eq!(config.fec_parity_ratio, 0.2);
300 assert_eq!(config.chunk_timeout_secs, 2);
301 assert_eq!(config.max_retries, 3);
302 assert_eq!(config.max_assemblies, 10);
303 }
304
305 #[test]
306 fn test_fibre_capabilities_default() {
307 let caps = FibreCapabilities::default();
308 assert!(caps.supports_fec);
309 assert_eq!(caps.max_chunk_size, DEFAULT_SHARD_SIZE);
310 assert!(caps.min_latency);
311 }
312}