Skip to main content

netget/server/bolt/
packstream.rs

1//! PackStream — Bolt's value encoding — and Bolt's message chunking.
2//!
3//! Hand-written because the whole codec is ~300 lines and the property that matters most is one
4//! no published crate states: **a peer cannot make the decoder recurse without bound or allocate
5//! what the bytes do not contain.** Two guards carry that:
6//!
7//! 1. **Depth.** Lists, maps and structures nest, and one marker byte opens a level (`0x91` is a
8//!    one-element list), so ~1 KB on the wire buys a thousand frames of recursion. [`decode`]
9//!    recurses with an explicit counter and refuses past [`MAX_PACKSTREAM_DEPTH`]. Real Bolt
10//!    traffic nests three or four deep (a RUN's parameter map holding a list of maps).
11//! 2. **Declared lengths.** A `0xD6` list header declares a 32-bit element count in four bytes.
12//!    Every count and byte length is checked against the bytes *remaining* before anything is
13//!    allocated: an element needs at least one byte, a map entry at least two, so a count larger
14//!    than that is refused as [`DecodeError::DeclaredLengthExceedsInput`] and
15//!    `Vec::with_capacity` is only ever asked for what could actually be present.
16//!
17//! Chunking ([`Dechunker`]) carries the third bound: a message is the concatenation of chunks
18//! (each a 2-byte big-endian size and that many bytes) ended by a zero-size chunk, and the total
19//! is capped at [`MAX_MESSAGE_BYTES`] as chunks arrive, so a peer that never sends the
20//! terminating zero cannot grow the buffer past it.
21//!
22//! Encoding always picks the smallest form: integers in `-16..=127` are one byte, strings under
23//! 16 bytes use the tiny marker, and so on. What NetGet encodes is either something it decoded
24//! (so already depth-bounded) or a JSON value the model wrote, which `bolt::values` bounds to the
25//! same depth before building it.
26
27/// Deepest nesting of lists, maps and structures the decoder accepts. A message's own top-level
28/// structure is depth 1, its fields depth 2, and so on.
29pub const MAX_PACKSTREAM_DEPTH: usize = 32;
30
31/// Largest message, summed over its chunks, the server accepts. Neo4j imposes no fixed limit of
32/// its own; 1 MiB is several thousand times what `cypher-shell` sends for an interactive query and
33/// still room for a RUN carrying a sizeable parameter map.
34pub const MAX_MESSAGE_BYTES: usize = 1024 * 1024;
35
36/// The largest payload one chunk can carry (its size field is a `u16`).
37pub const MAX_CHUNK_BYTES: usize = u16::MAX as usize;
38
39/// A PackStream value.
40///
41/// Maps keep their entries in wire order as a `Vec` — PackStream allows duplicate keys (the last
42/// wins) and Bolt's own metadata maps are small, so a hash map would buy nothing.
43#[derive(Debug, Clone, PartialEq)]
44pub enum Value {
45    Null,
46    Bool(bool),
47    Int(i64),
48    Float(f64),
49    Bytes(Vec<u8>),
50    String(String),
51    List(Vec<Value>),
52    Map(Vec<(String, Value)>),
53    Struct { tag: u8, fields: Vec<Value> },
54}
55
56impl Value {
57    /// Look a key up in a map value; the last occurrence wins, as the specification says.
58    pub fn get(&self, key: &str) -> Option<&Value> {
59        match self {
60            Value::Map(entries) => entries.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v),
61            _ => None,
62        }
63    }
64
65    pub fn as_str(&self) -> Option<&str> {
66        match self {
67            Value::String(s) => Some(s),
68            _ => None,
69        }
70    }
71
72    pub fn as_int(&self) -> Option<i64> {
73        match self {
74            Value::Int(i) => Some(*i),
75            _ => None,
76        }
77    }
78
79    /// Convenience: a string-keyed map from literal pairs.
80    pub fn map<K: Into<String>>(entries: impl IntoIterator<Item = (K, Value)>) -> Value {
81        Value::Map(entries.into_iter().map(|(k, v)| (k.into(), v)).collect())
82    }
83
84    pub fn string(s: impl Into<String>) -> Value {
85        Value::String(s.into())
86    }
87}
88
89/// Why a byte sequence is not a PackStream value.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum DecodeError {
92    /// The input ended inside a value.
93    UnexpectedEnd,
94    /// Nesting passed [`MAX_PACKSTREAM_DEPTH`].
95    TooDeep,
96    /// A marker byte in a range the specification reserves.
97    ReservedMarker(u8),
98    /// A map key that is not a string.
99    NonStringKey,
100    /// A string whose bytes are not UTF-8.
101    InvalidUtf8,
102    /// A size or count larger than the remaining input could possibly hold.
103    DeclaredLengthExceedsInput { declared: u64, remaining: usize },
104    /// Bytes left over after the one value a message holds.
105    TrailingBytes(usize),
106}
107
108impl std::fmt::Display for DecodeError {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            DecodeError::UnexpectedEnd => write!(f, "input ended inside a value"),
112            DecodeError::TooDeep => {
113                write!(f, "nesting deeper than {MAX_PACKSTREAM_DEPTH} levels")
114            }
115            DecodeError::ReservedMarker(m) => write!(f, "reserved marker byte 0x{m:02X}"),
116            DecodeError::NonStringKey => write!(f, "map key is not a string"),
117            DecodeError::InvalidUtf8 => write!(f, "string is not UTF-8"),
118            DecodeError::DeclaredLengthExceedsInput {
119                declared,
120                remaining,
121            } => write!(
122                f,
123                "declared length {declared} exceeds the {remaining} bytes remaining"
124            ),
125            DecodeError::TrailingBytes(n) => write!(f, "{n} bytes after the value"),
126        }
127    }
128}
129
130impl std::error::Error for DecodeError {}
131
132/// Decode exactly one value occupying the whole of `bytes`.
133pub fn decode(bytes: &[u8]) -> Result<Value, DecodeError> {
134    let mut pos = 0;
135    let value = decode_at(bytes, &mut pos, 1)?;
136    if pos != bytes.len() {
137        return Err(DecodeError::TrailingBytes(bytes.len() - pos));
138    }
139    Ok(value)
140}
141
142fn take<'a>(bytes: &'a [u8], pos: &mut usize, n: usize) -> Result<&'a [u8], DecodeError> {
143    let end = pos.checked_add(n).ok_or(DecodeError::UnexpectedEnd)?;
144    if end > bytes.len() {
145        return Err(DecodeError::UnexpectedEnd);
146    }
147    let slice = &bytes[*pos..end];
148    *pos = end;
149    Ok(slice)
150}
151
152fn read_uint(bytes: &[u8], pos: &mut usize, width: usize) -> Result<u64, DecodeError> {
153    let raw = take(bytes, pos, width)?;
154    Ok(raw.iter().fold(0u64, |acc, b| (acc << 8) | u64::from(*b)))
155}
156
157/// Refuse a declared count unless `count * min_bytes_each` could fit in what is left.
158fn check_declared(
159    bytes: &[u8],
160    pos: usize,
161    count: u64,
162    min_bytes_each: u64,
163) -> Result<usize, DecodeError> {
164    let remaining = bytes.len() - pos;
165    let needed = count.saturating_mul(min_bytes_each);
166    if needed > remaining as u64 {
167        return Err(DecodeError::DeclaredLengthExceedsInput {
168            declared: count,
169            remaining,
170        });
171    }
172    // Cannot truncate: `count <= remaining`, which is a usize.
173    Ok(count as usize)
174}
175
176fn decode_at(bytes: &[u8], pos: &mut usize, depth: usize) -> Result<Value, DecodeError> {
177    let marker = take(bytes, pos, 1)?[0];
178    match marker {
179        // TINY_INT, both halves.
180        0x00..=0x7F => Ok(Value::Int(i64::from(marker))),
181        0xF0..=0xFF => Ok(Value::Int(i64::from(marker as i8))),
182        0x80..=0x8F => decode_string(bytes, pos, u64::from(marker & 0x0F)),
183        0x90..=0x9F => decode_list(bytes, pos, u64::from(marker & 0x0F), depth),
184        0xA0..=0xAF => decode_map(bytes, pos, u64::from(marker & 0x0F), depth),
185        0xB0..=0xBF => decode_struct(bytes, pos, u64::from(marker & 0x0F), depth),
186        0xC0 => Ok(Value::Null),
187        0xC1 => {
188            let raw = take(bytes, pos, 8)?;
189            let mut buf = [0u8; 8];
190            buf.copy_from_slice(raw);
191            Ok(Value::Float(f64::from_be_bytes(buf)))
192        }
193        0xC2 => Ok(Value::Bool(false)),
194        0xC3 => Ok(Value::Bool(true)),
195        0xC8 => Ok(Value::Int(i64::from(take(bytes, pos, 1)?[0] as i8))),
196        0xC9 => {
197            let raw = take(bytes, pos, 2)?;
198            Ok(Value::Int(i64::from(i16::from_be_bytes([raw[0], raw[1]]))))
199        }
200        0xCA => {
201            let raw = take(bytes, pos, 4)?;
202            Ok(Value::Int(i64::from(i32::from_be_bytes([
203                raw[0], raw[1], raw[2], raw[3],
204            ]))))
205        }
206        0xCB => {
207            let raw = take(bytes, pos, 8)?;
208            let mut buf = [0u8; 8];
209            buf.copy_from_slice(raw);
210            Ok(Value::Int(i64::from_be_bytes(buf)))
211        }
212        0xCC..=0xCE => {
213            let width = 1usize << (marker - 0xCC);
214            let len = read_uint(bytes, pos, width)?;
215            let len = check_declared(bytes, *pos, len, 1)?;
216            Ok(Value::Bytes(take(bytes, pos, len)?.to_vec()))
217        }
218        0xD0..=0xD2 => {
219            let width = 1usize << (marker - 0xD0);
220            let len = read_uint(bytes, pos, width)?;
221            decode_string(bytes, pos, len)
222        }
223        0xD4..=0xD6 => {
224            let width = 1usize << (marker - 0xD4);
225            let count = read_uint(bytes, pos, width)?;
226            decode_list(bytes, pos, count, depth)
227        }
228        0xD8..=0xDA => {
229            let width = 1usize << (marker - 0xD8);
230            let count = read_uint(bytes, pos, width)?;
231            decode_map(bytes, pos, count, depth)
232        }
233        // STRUCT_8 / STRUCT_16 from PackStream v1: never produced by a Bolt 5 client, accepted
234        // for completeness.
235        0xDC => {
236            let count = read_uint(bytes, pos, 1)?;
237            decode_struct(bytes, pos, count, depth)
238        }
239        0xDD => {
240            let count = read_uint(bytes, pos, 2)?;
241            decode_struct(bytes, pos, count, depth)
242        }
243        other => Err(DecodeError::ReservedMarker(other)),
244    }
245}
246
247fn decode_string(bytes: &[u8], pos: &mut usize, len: u64) -> Result<Value, DecodeError> {
248    let len = check_declared(bytes, *pos, len, 1)?;
249    let raw = take(bytes, pos, len)?;
250    std::str::from_utf8(raw)
251        .map(|s| Value::String(s.to_string()))
252        .map_err(|_| DecodeError::InvalidUtf8)
253}
254
255fn decode_list(
256    bytes: &[u8],
257    pos: &mut usize,
258    count: u64,
259    depth: usize,
260) -> Result<Value, DecodeError> {
261    if depth > MAX_PACKSTREAM_DEPTH {
262        return Err(DecodeError::TooDeep);
263    }
264    let count = check_declared(bytes, *pos, count, 1)?;
265    let mut items = Vec::with_capacity(count);
266    for _ in 0..count {
267        items.push(decode_at(bytes, pos, depth + 1)?);
268    }
269    Ok(Value::List(items))
270}
271
272fn decode_map(
273    bytes: &[u8],
274    pos: &mut usize,
275    count: u64,
276    depth: usize,
277) -> Result<Value, DecodeError> {
278    if depth > MAX_PACKSTREAM_DEPTH {
279        return Err(DecodeError::TooDeep);
280    }
281    let count = check_declared(bytes, *pos, count, 2)?;
282    let mut entries = Vec::with_capacity(count);
283    for _ in 0..count {
284        let key = match decode_at(bytes, pos, depth + 1)? {
285            Value::String(s) => s,
286            _ => return Err(DecodeError::NonStringKey),
287        };
288        let value = decode_at(bytes, pos, depth + 1)?;
289        entries.push((key, value));
290    }
291    Ok(Value::Map(entries))
292}
293
294fn decode_struct(
295    bytes: &[u8],
296    pos: &mut usize,
297    count: u64,
298    depth: usize,
299) -> Result<Value, DecodeError> {
300    if depth > MAX_PACKSTREAM_DEPTH {
301        return Err(DecodeError::TooDeep);
302    }
303    let tag = take(bytes, pos, 1)?[0];
304    let count = check_declared(bytes, *pos, count, 1)?;
305    let mut fields = Vec::with_capacity(count);
306    for _ in 0..count {
307        fields.push(decode_at(bytes, pos, depth + 1)?);
308    }
309    Ok(Value::Struct { tag, fields })
310}
311
312/// Encode one value, choosing the smallest representation.
313pub fn encode(value: &Value, out: &mut Vec<u8>) {
314    match value {
315        Value::Null => out.push(0xC0),
316        Value::Bool(false) => out.push(0xC2),
317        Value::Bool(true) => out.push(0xC3),
318        Value::Int(i) => encode_int(*i, out),
319        Value::Float(f) => {
320            out.push(0xC1);
321            out.extend_from_slice(&f.to_be_bytes());
322        }
323        Value::Bytes(b) => {
324            encode_size(b.len(), [0xCC, 0xCD, 0xCE], None, out);
325            out.extend_from_slice(b);
326        }
327        Value::String(s) => {
328            encode_size(s.len(), [0xD0, 0xD1, 0xD2], Some(0x80), out);
329            out.extend_from_slice(s.as_bytes());
330        }
331        Value::List(items) => {
332            encode_size(items.len(), [0xD4, 0xD5, 0xD6], Some(0x90), out);
333            for item in items {
334                encode(item, out);
335            }
336        }
337        Value::Map(entries) => {
338            encode_size(entries.len(), [0xD8, 0xD9, 0xDA], Some(0xA0), out);
339            for (key, value) in entries {
340                encode(&Value::String(key.clone()), out);
341                encode(value, out);
342            }
343        }
344        Value::Struct { tag, fields } => {
345            let n = fields.len();
346            if n < 16 {
347                out.push(0xB0 | n as u8);
348            } else if n <= u8::MAX as usize {
349                out.push(0xDC);
350                out.push(n as u8);
351            } else {
352                out.push(0xDD);
353                out.extend_from_slice(&(n.min(u16::MAX as usize) as u16).to_be_bytes());
354            }
355            out.push(*tag);
356            for field in fields.iter().take(u16::MAX as usize) {
357                encode(field, out);
358            }
359        }
360    }
361}
362
363/// Encode to a fresh buffer.
364pub fn to_bytes(value: &Value) -> Vec<u8> {
365    let mut out = Vec::new();
366    encode(value, &mut out);
367    out
368}
369
370fn encode_int(i: i64, out: &mut Vec<u8>) {
371    if (-16..=127).contains(&i) {
372        out.push(i as i8 as u8);
373    } else if i8::try_from(i).is_ok() {
374        out.push(0xC8);
375        out.push(i as i8 as u8);
376    } else if let Ok(v) = i16::try_from(i) {
377        out.push(0xC9);
378        out.extend_from_slice(&v.to_be_bytes());
379    } else if let Ok(v) = i32::try_from(i) {
380        out.push(0xCA);
381        out.extend_from_slice(&v.to_be_bytes());
382    } else {
383        out.push(0xCB);
384        out.extend_from_slice(&i.to_be_bytes());
385    }
386}
387
388/// Write a size header: the tiny marker when there is one and `len < 16`, else the 8/16/32-bit
389/// form. Lengths past `u32::MAX` cannot occur for anything NetGet builds (messages are capped at
390/// [`MAX_MESSAGE_BYTES`] on the way in and model answers are far smaller on the way out).
391fn encode_size(len: usize, wide: [u8; 3], tiny: Option<u8>, out: &mut Vec<u8>) {
392    match tiny {
393        Some(base) if len < 16 => out.push(base | len as u8),
394        _ if len <= u8::MAX as usize => {
395            out.push(wide[0]);
396            out.push(len as u8);
397        }
398        _ if len <= u16::MAX as usize => {
399            out.push(wide[1]);
400            out.extend_from_slice(&(len as u16).to_be_bytes());
401        }
402        _ => {
403            out.push(wide[2]);
404            out.extend_from_slice(&(len.min(u32::MAX as usize) as u32).to_be_bytes());
405        }
406    }
407}
408
409/// Frame one encoded message as chunks of at most [`MAX_CHUNK_BYTES`], ended by `00 00`.
410pub fn chunk(message: &[u8]) -> Vec<u8> {
411    let mut out = Vec::with_capacity(message.len() + 4 + 2 * (message.len() / MAX_CHUNK_BYTES));
412    for piece in message.chunks(MAX_CHUNK_BYTES) {
413        out.extend_from_slice(&(piece.len() as u16).to_be_bytes());
414        out.extend_from_slice(piece);
415    }
416    out.extend_from_slice(&[0, 0]);
417    out
418}
419
420/// Encode a value and frame it as one chunked message.
421pub fn message_bytes(value: &Value) -> Vec<u8> {
422    chunk(&to_bytes(value))
423}
424
425/// Why the byte stream is not a sequence of acceptable messages.
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub enum FrameError {
428    /// The message's chunks summed past the cap.
429    TooLarge { limit: usize },
430}
431
432/// Reassembles chunked messages from a byte stream, enforcing [`MAX_MESSAGE_BYTES`] as chunks
433/// arrive.
434///
435/// Bytes are appended with [`push`](Self::push); [`next_message`](Self::next_message) returns a
436/// message once its terminating zero chunk has been seen. Zero chunks between messages are
437/// Bolt's NOOP keep-alive and are skipped. A chunk is moved out of the input buffer as soon as it
438/// is complete, so the input holds at most one partial chunk plus whatever the last read added.
439#[derive(Debug)]
440pub struct Dechunker {
441    input: Vec<u8>,
442    message: Vec<u8>,
443    limit: usize,
444}
445
446impl Dechunker {
447    pub fn new(limit: usize) -> Self {
448        Self {
449            input: Vec::new(),
450            message: Vec::new(),
451            limit,
452        }
453    }
454
455    pub fn push(&mut self, bytes: &[u8]) {
456        self.input.extend_from_slice(bytes);
457    }
458
459    /// Bytes buffered that have not yet become part of a complete message.
460    pub fn pending(&self) -> usize {
461        self.input.len() + self.message.len()
462    }
463
464    pub fn next_message(&mut self) -> Result<Option<Vec<u8>>, FrameError> {
465        let mut consumed = 0usize;
466        let result = loop {
467            let rest = &self.input[consumed..];
468            if rest.len() < 2 {
469                break Ok(None);
470            }
471            let size = usize::from(u16::from_be_bytes([rest[0], rest[1]]));
472            if size == 0 {
473                consumed += 2;
474                if self.message.is_empty() {
475                    continue; // NOOP between messages
476                }
477                break Ok(Some(std::mem::take(&mut self.message)));
478            }
479            // Refuse on the declared size, before waiting for the bytes it announces.
480            if self.message.len() + size > self.limit {
481                break Err(FrameError::TooLarge { limit: self.limit });
482            }
483            if rest.len() < 2 + size {
484                break Ok(None);
485            }
486            self.message.extend_from_slice(&rest[2..2 + size]);
487            consumed += 2 + size;
488        };
489        self.input.drain(..consumed);
490        result
491    }
492}