Skip to main content

streaming_crypto/core_api/stream_v2/framing/
encode.rs

1use byteorder::{LittleEndian, WriteBytesExt};
2use bytes::{BytesMut, BufMut};
3
4use crate::stream_v2::framing::types::{FRAME_VERSION, FRAME_MAGIC};
5use crate::stream_v2::framing::types::{FrameHeader, FrameError};
6
7/// Encode a frame record into canonical wire format.
8///
9/// Layout:
10///
11/// ```text
12/// [ magic (4) ]
13/// [ version (1) ]
14/// [ frame_type (1) ]
15/// [ segment_index (4) ]
16/// [ frame_index (4) ]
17/// [ plaintext_len (4) ]
18/// [ ciphertext_len (4) ]
19/// [ ciphertext (M) ]
20/// ```
21pub fn encode_frame(
22    header: &FrameHeader,
23    ciphertext: &[u8],
24) -> Result<Vec<u8>, FrameError> {
25    let expected = FrameHeader::LEN + header.ciphertext_len() as usize;
26
27    if ciphertext.len() != header.ciphertext_len() as usize {
28        return Err(FrameError::LengthMismatch {
29            expected,
30            actual: ciphertext.len(),
31        });
32    }
33
34    let mut wire = Vec::with_capacity(expected);
35
36    // --- Header ---
37    wire.extend_from_slice(&FRAME_MAGIC);
38    wire.push(FRAME_VERSION);
39    wire.push(header.frame_type().try_to_u8()?);
40
41    wire.write_u32::<LittleEndian>(header.segment_index()).unwrap();
42    wire.write_u32::<LittleEndian>(header.frame_index()).unwrap();
43    wire.write_u32::<LittleEndian>(header.plaintext_len()).unwrap();
44    wire.write_u32::<LittleEndian>(header.ciphertext_len()).unwrap();
45
46    // --- Body ---
47    wire.extend_from_slice(ciphertext);
48
49    // --- Validation ---
50    debug_assert_eq!(wire.len(), expected);
51
52    Ok(wire)
53
54    // ### 🔥 Why this is better
55
56    // * Encoding no longer **requires ownership**
57    // * Ciphertext can be:
58
59    // * `Vec<u8>`
60    // * `Bytes`
61    // * slice from another buffer
62    // * Header + body are **logically separated**
63}
64
65// ### In‑Place Encode
66
67#[inline]
68pub fn encode_in_place(
69    header: &FrameHeader,
70    ciphertext: &[u8],
71    buf: &mut BytesMut, // caller provides buffer
72) -> Result<(), FrameError> {
73    let expected = FrameHeader::LEN + header.ciphertext_len() as usize;
74
75    if ciphertext.len() != header.ciphertext_len() as usize {
76        return Err(FrameError::LengthMismatch {
77            expected,
78            actual: ciphertext.len(),
79        });
80    }
81
82    buf.clear();
83    buf.reserve(expected);
84
85    // --- Header ---
86    buf.put_slice(&FRAME_MAGIC);
87    buf.put_u8(FRAME_VERSION);
88    buf.put_u8(header.frame_type().try_to_u8()?);
89
90    buf.put_u32_le(header.segment_index());
91    buf.put_u32_le(header.frame_index());
92    buf.put_u32_le(header.plaintext_len());
93    buf.put_u32_le(header.ciphertext_len());
94
95    // --- Body ---
96    buf.put_slice(ciphertext);
97
98    debug_assert_eq!(buf.len(), expected);
99
100    Ok(())
101}
102
103// ### Key Differences
104// - **Caller provides buffer**: `encode_frame_in_place` writes directly into a `BytesMut` supplied by the caller, instead of returning a new `Vec<u8>`.
105// - **No extra allocations**: We control buffer reuse (e.g., via a slab allocator), so repeated calls don’t churn the allocator.
106// - **Decode stays zero‑copy**: It just slices into the provided wire buffer, no new allocations.
107
108// ### Usage Example
109// ```rust
110// let mut buf = BytesMut::with_capacity(FrameHeader::LEN + ciphertext.len());
111// encode_frame_in_place(&header, &ciphertext, &mut buf)?;
112// let wire = buf.freeze();
113
114// let view = decode_frame_in_place(&wire)?;
115// ```