Skip to main content

logicaffeine_compile/concurrency/
stream.rs

1//! Streaming framing / deframing — process a byte STREAM of length-delimited messages
2//! INCREMENTALLY, as bytes arrive, without ever buffering the whole stream. This is the basis of
3//! every streaming protocol (gRPC, Arrow Flight, Kafka): a producer frames each message as
4//! `[uvarint length][body]`; a consumer feeds arriving chunks — ANY chunking, from byte-at-a-time
5//! to many frames at once to a frame split across chunks — to a [`StreamDeframer`], which hands
6//! back each complete frame the instant it is buffered, ZERO-COPY (the body is a borrowed slice, so
7//! pair it with [`crate::concurrency::marshal::view_message`] for a fully in-place stream read).
8//!
9//! Paired with the zero-copy `WireView` receive, this closes Cap'n Proto's streaming claim:
10//! incremental reads with no whole-stream buffering and no decode of untouched fields.
11
12/// Frame `body` for the stream: a LEB128 length prefix, then the bytes. Append-only into `out`, so
13/// a producer can concatenate many frames into one stream buffer.
14pub fn frame_for_stream(body: &[u8], out: &mut Vec<u8>) {
15    let mut len = body.len() as u64;
16    loop {
17        let mut byte = (len & 0x7f) as u8;
18        len >>= 7;
19        if len != 0 {
20            byte |= 0x80;
21        }
22        out.push(byte);
23        if len == 0 {
24            break;
25        }
26    }
27    out.extend_from_slice(body);
28}
29
30/// An incremental deframer. Feed it arriving bytes with [`push`](Self::push); pull complete frames
31/// with [`drain_frames`](Self::drain_frames). Holds only the bytes not yet delivered (a partial
32/// frame at most), so memory stays bounded regardless of stream length.
33#[derive(Default)]
34pub struct StreamDeframer {
35    buf: Vec<u8>,
36    start: usize,
37}
38
39impl StreamDeframer {
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Append a newly-arrived chunk (any size, including empty).
45    pub fn push(&mut self, bytes: &[u8]) {
46        self.buf.extend_from_slice(bytes);
47    }
48
49    /// No un-delivered bytes remain (the stream is fully consumed up to a frame boundary).
50    pub fn is_empty(&self) -> bool {
51        self.start >= self.buf.len()
52    }
53
54    /// Bytes buffered but not yet delivered (a partial frame awaiting more input).
55    pub fn pending(&self) -> usize {
56        self.buf.len() - self.start
57    }
58
59    /// Hand every COMPLETE frame currently buffered, in arrival order, to `f` as a borrowed body
60    /// slice (zero-copy). Stops at the first incomplete frame — keeping its partial bytes for the
61    /// next [`push`](Self::push) — then compacts the consumed prefix so the buffer never grows
62    /// unbounded. Returns how many frames were delivered.
63    pub fn drain_frames(&mut self, mut f: impl FnMut(&[u8])) -> usize {
64        let mut count = 0;
65        loop {
66            let Some((len, header)) = read_uvarint(&self.buf[self.start..]) else {
67                break; // length prefix not fully arrived
68            };
69            let body_start = self.start + header;
70            let Some(body_end) = body_start.checked_add(len as usize) else {
71                break;
72            };
73            if body_end > self.buf.len() {
74                break; // body not fully arrived
75            }
76            f(&self.buf[body_start..body_end]);
77            self.start = body_end;
78            count += 1;
79        }
80        if self.start > 0 {
81            self.buf.drain(0..self.start);
82            self.start = 0;
83        }
84        count
85    }
86}
87
88/// LEB128 uvarint from the front of `buf`: `Some((value, bytes_consumed))`, or `None` if the varint
89/// is truncated mid-encoding (wait for more bytes — never a panic, never a misread).
90fn read_uvarint(buf: &[u8]) -> Option<(u64, usize)> {
91    let mut value = 0u64;
92    let mut shift = 0u32;
93    for (i, &b) in buf.iter().enumerate() {
94        value |= u64::from(b & 0x7f) << shift;
95        if b & 0x80 == 0 {
96            return Some((value, i + 1));
97        }
98        shift += 7;
99        if shift >= 64 {
100            return None; // malformed over-long varint — treat as "wait" (fail-closed)
101        }
102    }
103    None
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    fn frame_all(bodies: &[&[u8]]) -> Vec<u8> {
111        let mut out = Vec::new();
112        for b in bodies {
113            frame_for_stream(b, &mut out);
114        }
115        out
116    }
117
118    fn collect(deframer: &mut StreamDeframer) -> Vec<Vec<u8>> {
119        let mut got = Vec::new();
120        deframer.drain_frames(|body| got.push(body.to_vec()));
121        got
122    }
123
124    #[test]
125    fn whole_stream_at_once() {
126        let stream = frame_all(&[b"hello", b"", b"world!!", &[0u8, 255, 7]]);
127        let mut d = StreamDeframer::new();
128        d.push(&stream);
129        let got = collect(&mut d);
130        assert_eq!(got, vec![b"hello".to_vec(), b"".to_vec(), b"world!!".to_vec(), vec![0, 255, 7]]);
131        assert!(d.is_empty(), "fully consumed at a frame boundary");
132    }
133
134    #[test]
135    fn byte_at_a_time_arrival() {
136        // The hardest streaming case: one byte per chunk. Frames must still emerge, in order, the
137        // instant each is complete — and never before.
138        let bodies: Vec<Vec<u8>> = (0..20).map(|i| vec![i as u8; (i % 5) + 1]).collect();
139        let refs: Vec<&[u8]> = bodies.iter().map(|b| b.as_slice()).collect();
140        let stream = frame_all(&refs);
141
142        let mut d = StreamDeframer::new();
143        let mut got = Vec::new();
144        for &byte in &stream {
145            d.push(&[byte]);
146            d.drain_frames(|body| got.push(body.to_vec()));
147        }
148        assert_eq!(got, bodies, "every frame reassembled exactly, in order");
149        assert!(d.is_empty());
150    }
151
152    #[test]
153    fn frame_split_across_chunks_mid_body_and_mid_length() {
154        // A long body whose length prefix is itself multi-byte, split at adversarial points.
155        let big = vec![0xABu8; 5000]; // length prefix = 2 bytes (varint)
156        let stream = frame_all(&[b"a", big.as_slice(), b"z"]);
157
158        let mut d = StreamDeframer::new();
159        // Split 1: only the first length byte of the big frame (after "a"'s frame + big's 1st len byte).
160        let cut1 = 2 + 1; // "a" frame is [len=1][a] = 2 bytes; +1 byte into big's length varint
161        d.push(&stream[..cut1]);
162        assert_eq!(collect(&mut d), vec![b"a".to_vec()], "only the complete first frame");
163        // Split 2: partway into the big body.
164        let cut2 = cut1 + 2500;
165        d.push(&stream[cut1..cut2]);
166        assert_eq!(collect(&mut d), Vec::<Vec<u8>>::new(), "big body incomplete → nothing yet");
167        assert!(d.pending() > 0, "the partial big frame is buffered");
168        // The rest.
169        d.push(&stream[cut2..]);
170        assert_eq!(collect(&mut d), vec![big.clone(), b"z".to_vec()], "big + trailing frame complete");
171        assert!(d.is_empty());
172    }
173
174    #[test]
175    fn empty_and_no_complete_frame() {
176        let mut d = StreamDeframer::new();
177        assert_eq!(collect(&mut d), Vec::<Vec<u8>>::new(), "no bytes → nothing");
178        d.push(&[0x80]); // an incomplete length varint (continuation bit, no follow byte)
179        assert_eq!(collect(&mut d), Vec::<Vec<u8>>::new(), "incomplete length → wait");
180        assert_eq!(d.pending(), 1);
181        d.push(&[0x01, b'X']); // completes len = 0x80|0x01<<7 = 128 → but body is 1 byte → still incomplete
182        assert_eq!(collect(&mut d), Vec::<Vec<u8>>::new(), "length completes but body not arrived");
183    }
184
185    #[test]
186    fn end_to_end_zero_copy_stream_read() {
187        // The point: stream real wire messages and read a field of each IN PLACE (view_message over
188        // the borrowed frame body — no per-frame decode, no whole-stream buffer).
189        use crate::concurrency::marshal::{message_to_wire, view_message};
190        use crate::interpreter::RuntimeValue;
191
192        let m1 = message_to_wire("p", &RuntimeValue::Int(11)).unwrap();
193        let m2 = message_to_wire("p", &RuntimeValue::Int(22)).unwrap();
194        let m3 = message_to_wire("p", &RuntimeValue::Int(33)).unwrap();
195        let stream = frame_all(&[&m1, &m2, &m3]);
196
197        let mut d = StreamDeframer::new();
198        let mut ints = Vec::new();
199        // Deliver in awkward 7-byte chunks to force cross-frame splits.
200        for chunk in stream.chunks(7) {
201            d.push(chunk);
202            d.drain_frames(|frame| {
203                let v = view_message(frame).expect("frame opens as a view in place");
204                ints.push(v.as_int().expect("reads the int with no full decode"));
205            });
206        }
207        assert_eq!(ints, vec![11, 22, 33], "every streamed message read zero-copy, in order");
208    }
209}