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}