Skip to main content

blvm_protocol/
fibre.rs

1//! FIBRE: Fast Internet Bitcoin Relay Engine - Protocol Definitions
2//!
3//! This module provides FIBRE protocol packet format definitions, serialization,
4//! and protocol-level types. Transport implementation is in blvm-node.
5
6use crate::Hash;
7use serde::{Deserialize, Serialize};
8
9/// FIBRE packet format constants
10pub const FIBRE_MAGIC: [u8; 4] = [0xF1, 0xB3, 0xE0, 0x00]; // "FIBRE" in hex-like
11pub 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; // Ethernet MTU
17pub const HEADER_SIZE: usize = 62;
18pub const MAX_DATA_SIZE: usize = MAX_PACKET_SIZE - HEADER_SIZE - 4; // 1434 bytes
19pub const DEFAULT_SHARD_SIZE: usize = 1400; // UDP MTU - headers
20
21/// FEC chunk with full metadata for FIBRE transmission
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct FecChunk {
24    /// Chunk index (0-based, includes both data and parity shards)
25    pub index: u32,
26    /// Total number of chunks (data + parity)
27    pub total_chunks: u32,
28    /// Number of data chunks (before parity)
29    pub data_chunks: u32,
30    /// Chunk data (FEC-encoded shard)
31    pub data: Vec<u8>,
32    /// Chunk size in bytes
33    pub size: usize,
34    /// Block hash (for validation and assembly)
35    pub block_hash: Hash,
36    /// Sequence number (for ordering and duplicate detection)
37    pub sequence: u64,
38    /// Magic bytes for packet validation
39    pub magic: [u8; 4],
40}
41
42impl FecChunk {
43    /// Serialize chunk to FIBRE packet format
44    pub fn serialize(&self) -> Result<Vec<u8>, FibreProtocolError> {
45        let mut packet = Vec::with_capacity(HEADER_SIZE + self.data.len() + 4);
46
47        // Magic
48        packet.extend_from_slice(&FIBRE_MAGIC);
49        // Version
50        packet.push(FIBRE_VERSION);
51        // Type
52        packet.push(PACKET_TYPE_CHUNK);
53        // Block hash
54        packet.extend_from_slice(&self.block_hash);
55        // Sequence
56        packet.extend_from_slice(&self.sequence.to_be_bytes());
57        // Chunk index
58        packet.extend_from_slice(&self.index.to_be_bytes());
59        // Total chunks
60        packet.extend_from_slice(&self.total_chunks.to_be_bytes());
61        // Data chunks
62        packet.extend_from_slice(&self.data_chunks.to_be_bytes());
63        // Data length
64        packet.extend_from_slice(&(self.data.len() as u32).to_be_bytes());
65        // Data
66        packet.extend_from_slice(&self.data);
67
68        // Checksum (CRC32)
69        let checksum = crc32fast::hash(&packet);
70        packet.extend_from_slice(&checksum.to_be_bytes());
71
72        Ok(packet)
73    }
74
75    /// Deserialize chunk from FIBRE packet format
76    pub fn deserialize(data: &[u8]) -> Result<Self, FibreProtocolError> {
77        // Validate minimum size
78        if data.len() < HEADER_SIZE + 4 {
79            return Err(FibreProtocolError::InvalidPacket(
80                "Packet too short".to_string(),
81            ));
82        }
83
84        // Verify magic
85        if data[0..4] != FIBRE_MAGIC {
86            return Err(FibreProtocolError::InvalidPacket(
87                "Invalid magic bytes".to_string(),
88            ));
89        }
90
91        // Verify checksum
92        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        // Parse fields
106        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        // Validate data length
130        if data.len() < HEADER_SIZE + data_length + 4 {
131            return Err(FibreProtocolError::InvalidPacket(
132                "Packet data length mismatch".to_string(),
133            ));
134        }
135
136        // Extract data
137        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/// FIBRE capabilities advertised by peers
154#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
155pub struct FibreCapabilities {
156    /// Supports FEC encoding
157    pub supports_fec: bool,
158    /// Maximum chunk size
159    pub max_chunk_size: usize,
160    /// Minimum latency preference
161    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/// FIBRE protocol configuration
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct FibreConfig {
177    /// Enable FIBRE relay
178    #[serde(default = "default_true")]
179    pub enabled: bool,
180    /// FEC parity ratio (0.0-1.0, default: 0.2 = 20% parity)
181    #[serde(default = "default_fec_parity_ratio")]
182    pub fec_parity_ratio: f64,
183    /// Chunk retransmission timeout (seconds)
184    #[serde(default = "default_chunk_timeout")]
185    pub chunk_timeout_secs: u64,
186    /// Maximum retransmission attempts
187    #[serde(default = "default_max_retries")]
188    pub max_retries: u32,
189    /// Maximum concurrent block assemblies
190    #[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/// FIBRE protocol errors
223#[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]); // Invalid magic
265
266        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        // Corrupt checksum
287        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}