kona_protocol/
frame.rs

1//! Frame types for OP Stack L2 data transmission.
2//!
3//! Frames are the fundamental unit of data transmission in the OP Stack derivation
4//! pipeline. They provide a way to split large channel data into smaller, manageable
5//! chunks that can be transmitted via L1 transactions and later reassembled.
6//!
7//! # Frame Structure
8//!
9//! Each frame contains:
10//! - **Channel ID**: Unique identifier linking frames to their parent channel
11//! - **Frame Number**: Sequence number for ordering and reassembly
12//! - **Data Payload**: The actual frame data content
13//! - **Last Flag**: Indicates if this is the final frame in the sequence
14//!
15//! # Transmission Process
16//!
17//! ```text
18//! Channel Data → Split into Frames → Transmit via L1 → Reassemble Channel
19//! ```
20//!
21//! # Error Handling
22//!
23//! Frame processing can fail due to:
24//! - Size constraints (too large or too small)
25//! - Invalid frame identifiers or numbers
26//! - Data length mismatches
27//! - Unsupported versions
28
29use crate::ChannelId;
30use alloc::vec::Vec;
31
32/// Version identifier for the current derivation pipeline format.
33///
34/// This constant defines the version of the derivation pipeline protocol
35/// that this implementation supports. It's included in frame encoding to
36/// ensure compatibility and enable future protocol upgrades.
37pub const DERIVATION_VERSION_0: u8 = 0;
38
39/// Overhead estimation for frame metadata and tagging information.
40///
41/// This constant provides an estimate of the additional bytes required
42/// for frame metadata (channel ID, frame number, data length, flags) and
43/// L1 transaction overhead. Used for buffer size calculations and gas
44/// estimation when planning frame transmission.
45pub const FRAME_OVERHEAD: usize = 200;
46
47/// Maximum allowed size for a single frame in bytes.
48///
49/// While typical L1 transactions carrying frames are around 128 KB due to
50/// network conditions and gas limits, this larger limit provides headroom
51/// for future growth as L1 gas limits and network conditions improve.
52///
53/// The 1MB limit balances:
54/// - **Transmission efficiency**: Larger frames reduce overhead
55/// - **Network compatibility**: Must fit within reasonable L1 transaction sizes
56/// - **Memory constraints**: Avoid excessive memory usage during processing
57pub const MAX_FRAME_LEN: usize = 1_000_000;
58
59/// A frame decoding error.
60#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq, Hash)]
61pub enum FrameDecodingError {
62    /// The frame data is too large.
63    #[error("Frame data too large: {0} bytes")]
64    DataTooLarge(usize),
65    /// The frame data is too short.
66    #[error("Frame data too short: {0} bytes")]
67    DataTooShort(usize),
68    /// Error decoding the frame id.
69    #[error("Invalid frame id")]
70    InvalidId,
71    /// Error decoding the frame number.
72    #[error("Invalid frame number")]
73    InvalidNumber,
74    /// Error decoding the frame data length.
75    #[error("Invalid frame data length")]
76    InvalidDataLength,
77}
78
79/// Frame parsing error.
80#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq, Hash)]
81pub enum FrameParseError {
82    /// Error parsing the frame data.
83    #[error("Frame decoding error: {0}")]
84    FrameDecodingError(FrameDecodingError),
85    /// No frames to parse.
86    #[error("No frames to parse")]
87    NoFrames,
88    /// Unsupported derivation version.
89    #[error("Unsupported derivation version")]
90    UnsupportedVersion,
91    /// Frame data length mismatch.
92    #[error("Frame data length mismatch")]
93    DataLengthMismatch,
94    /// No frames decoded.
95    #[error("No frames decoded")]
96    NoFramesDecoded,
97}
98
99/// A channel frame representing a segment of channel data for transmission.
100///
101/// Frames are the atomic units of data transmission in the OP Stack derivation pipeline.
102/// Large channel data is split into multiple frames to fit within L1 transaction size
103/// constraints while maintaining the ability to reassemble the original data.
104///
105/// # Binary Encoding Format
106///
107/// The frame is encoded as a concatenated byte sequence:
108/// ```text
109/// frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last
110/// ```
111///
112/// ## Field Specifications
113/// - **channel_id** (16 bytes): Unique identifier linking this frame to its parent channel
114/// - **frame_number** (2 bytes, uint16): Sequence number for proper reassembly ordering
115/// - **frame_data_length** (4 bytes, uint32): Length of the frame_data field in bytes
116/// - **frame_data** (variable): The actual payload data for this frame segment
117/// - **is_last** (1 byte, bool): Flag indicating if this is the final frame in the sequence
118///
119/// ## Total Overhead
120/// Each frame has a fixed overhead of 23 bytes (16 + 2 + 4 + 1) plus the variable data payload.
121///
122/// # Frame Sequencing
123///
124/// Frames within a channel must be:
125/// 1. **Sequentially numbered**: Starting from 0 and incrementing by 1
126/// 2. **Properly terminated**: Exactly one frame marked with `is_last = true`
127/// 3. **Complete**: All frame numbers from 0 to the last frame must be present
128///
129/// # Reassembly Process
130///
131/// Channel reassembly involves:
132/// 1. **Collection**: Gather all frames with the same channel ID
133/// 2. **Sorting**: Order frames by their frame number
134/// 3. **Validation**: Verify sequential numbering and last frame flag
135/// 4. **Concatenation**: Combine frame data in order to reconstruct channel
136///
137/// # Error Conditions
138///
139/// Frame processing can fail due to:
140/// - Missing frames in the sequence
141/// - Duplicate frame numbers
142/// - Multiple frames marked as last
143/// - Frame data exceeding size limits
144/// - Invalid encoding or corruption
145#[derive(Debug, Clone, PartialEq, Eq, Default)]
146pub struct Frame {
147    /// Unique identifier linking this frame to its parent channel.
148    ///
149    /// All frames belonging to the same channel share this identifier,
150    /// enabling proper grouping during the reassembly process. The channel
151    /// ID is typically derived from the first frame's metadata.
152    pub id: ChannelId,
153    /// Sequence number for frame ordering within the channel.
154    ///
155    /// Frame numbers start at 0 and increment sequentially. This field
156    /// is critical for proper reassembly ordering and detecting missing
157    /// or duplicate frames during channel reconstruction.
158    pub number: u16,
159    /// Payload data carried by this frame.
160    ///
161    /// Contains a segment of the original channel data. When all frames
162    /// in a channel are reassembled, concatenating this data in frame
163    /// number order reconstructs the complete channel payload.
164    pub data: Vec<u8>,
165    /// Flag indicating whether this is the final frame in the channel sequence.
166    ///
167    /// Exactly one frame per channel should have this flag set to `true`.
168    /// This enables detection of complete channel reception and validation
169    /// that no frames are missing from the end of the sequence.
170    pub is_last: bool,
171}
172
173impl Frame {
174    /// Creates a new [`Frame`].
175    pub const fn new(id: ChannelId, number: u16, data: Vec<u8>, is_last: bool) -> Self {
176        Self { id, number, data, is_last }
177    }
178
179    /// Encode the frame into a byte vector.
180    pub fn encode(&self) -> Vec<u8> {
181        let mut encoded = Vec::with_capacity(16 + 2 + 4 + self.data.len() + 1);
182        encoded.extend_from_slice(&self.id);
183        encoded.extend_from_slice(&self.number.to_be_bytes());
184        encoded.extend_from_slice(&(self.data.len() as u32).to_be_bytes());
185        encoded.extend_from_slice(&self.data);
186        encoded.push(self.is_last as u8);
187        encoded
188    }
189
190    /// Decode a frame from a byte vector.
191    pub fn decode(encoded: &[u8]) -> Result<(usize, Self), FrameDecodingError> {
192        const BASE_FRAME_LEN: usize = 16 + 2 + 4 + 1;
193
194        if encoded.len() < BASE_FRAME_LEN {
195            return Err(FrameDecodingError::DataTooShort(encoded.len()));
196        }
197
198        let id = encoded[..16].try_into().map_err(|_| FrameDecodingError::InvalidId)?;
199        let number = u16::from_be_bytes(
200            encoded[16..18].try_into().map_err(|_| FrameDecodingError::InvalidNumber)?,
201        );
202        let data_len = u32::from_be_bytes(
203            encoded[18..22].try_into().map_err(|_| FrameDecodingError::InvalidDataLength)?,
204        ) as usize;
205
206        if data_len > MAX_FRAME_LEN || data_len >= encoded.len() - (BASE_FRAME_LEN - 1) {
207            return Err(FrameDecodingError::DataTooLarge(data_len));
208        }
209
210        let data = encoded[22..22 + data_len].to_vec();
211        let is_last = encoded[22 + data_len] == 1;
212        Ok((BASE_FRAME_LEN + data_len, Self { id, number, data, is_last }))
213    }
214
215    /// Parses a single frame from the given data at the given starting position,
216    /// returning the frame and the number of bytes consumed.
217    pub fn parse_frame(data: &[u8], start: usize) -> Result<(usize, Self), FrameDecodingError> {
218        let (frame_len, frame) = Self::decode(&data[start..])?;
219        Ok((frame_len, frame))
220    }
221
222    /// Parse the on chain serialization of frame(s) in an L1 transaction. Currently
223    /// only version 0 of the serialization format is supported. All frames must be parsed
224    /// without error and there must not be any left over data and there must be at least one
225    /// frame.
226    ///
227    /// Frames are stored in L1 transactions with the following format:
228    /// * `data = DerivationVersion0 ++ Frame(s)` Where there is one or more frames concatenated
229    ///   together.
230    pub fn parse_frames(encoded: &[u8]) -> Result<Vec<Self>, FrameParseError> {
231        if encoded.is_empty() {
232            return Err(FrameParseError::NoFrames);
233        }
234        if encoded[0] != DERIVATION_VERSION_0 {
235            return Err(FrameParseError::UnsupportedVersion);
236        }
237
238        let data = &encoded[1..];
239        let mut frames = Vec::new();
240        let mut offset = 0;
241        while offset < data.len() {
242            let (frame_length, frame) =
243                Self::decode(&data[offset..]).map_err(FrameParseError::FrameDecodingError)?;
244            frames.push(frame);
245            offset += frame_length;
246        }
247
248        if offset != data.len() {
249            return Err(FrameParseError::DataLengthMismatch);
250        }
251        if frames.is_empty() {
252            return Err(FrameParseError::NoFramesDecoded);
253        }
254
255        Ok(frames)
256    }
257
258    /// Calculates the size of the frame + overhead for storing the frame. The sum of the frame size
259    /// of each frame in a channel determines the channel's size. The sum of the channel sizes
260    /// is used for pruning & compared against the max channel bank size.
261    pub fn size(&self) -> usize {
262        self.data.len() + FRAME_OVERHEAD
263    }
264}
265
266#[cfg(test)]
267mod test {
268    use super::*;
269    use alloc::vec;
270
271    #[test]
272    fn test_encode_frame_roundtrip() {
273        let frame = Frame { id: [0xFF; 16], number: 0xEE, data: vec![0xDD; 50], is_last: true };
274
275        let (_, frame_decoded) = Frame::decode(&frame.encode()).unwrap();
276        assert_eq!(frame, frame_decoded);
277    }
278
279    #[test]
280    fn test_data_too_short() {
281        let frame = Frame { id: [0xFF; 16], number: 0xEE, data: vec![0xDD; 22], is_last: true };
282        let err = Frame::decode(&frame.encode()[..22]).unwrap_err();
283        assert_eq!(err, FrameDecodingError::DataTooShort(22));
284    }
285
286    #[test]
287    fn test_decode_exceeds_max_data_len() {
288        let frame = Frame {
289            id: [0xFF; 16],
290            number: 0xEE,
291            data: vec![0xDD; MAX_FRAME_LEN + 1],
292            is_last: true,
293        };
294        let err = Frame::decode(&frame.encode()).unwrap_err();
295        assert_eq!(err, FrameDecodingError::DataTooLarge(MAX_FRAME_LEN + 1));
296    }
297
298    #[test]
299    fn test_decode_malicious_data_len() {
300        let frame = Frame { id: [0xFF; 16], number: 0xEE, data: vec![0xDD; 50], is_last: true };
301        let mut encoded = frame.encode();
302        let data_len = (encoded.len() - 22) as u32;
303        encoded[18..22].copy_from_slice(&data_len.to_be_bytes());
304
305        let err = Frame::decode(&encoded).unwrap_err();
306        assert_eq!(err, FrameDecodingError::DataTooLarge(encoded.len() - 22_usize));
307
308        let valid_data_len = (encoded.len() - 23) as u32;
309        encoded[18..22].copy_from_slice(&valid_data_len.to_be_bytes());
310        let (_, frame_decoded) = Frame::decode(&encoded).unwrap();
311        assert_eq!(frame, frame_decoded);
312    }
313
314    #[test]
315    fn test_decode_many() {
316        let frame = Frame { id: [0xFF; 16], number: 0xEE, data: vec![0xDD; 50], is_last: true };
317        let mut bytes = Vec::new();
318        bytes.extend_from_slice(&[DERIVATION_VERSION_0]);
319        (0..5).for_each(|_| {
320            bytes.extend_from_slice(&frame.encode());
321        });
322
323        let frames = Frame::parse_frames(bytes.as_slice()).unwrap();
324        assert_eq!(frames.len(), 5);
325        (0..5).for_each(|i| {
326            assert_eq!(frames[i], frame);
327        });
328    }
329}