Skip to main content

nodedb_query/msgpack_scan/reader/
scalar.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Typed scalar reads and container headers.
4
5use std::str;
6
7use super::tags::*;
8
9/// Read an f64 from the value at `offset`. Handles float32, float64,
10/// and all integer types (coerced to f64).
11pub fn read_f64(buf: &[u8], offset: usize) -> Option<f64> {
12    let tag = get(buf, offset)?;
13    match tag {
14        // positive fixint
15        0x00..=0x7f => Some(tag as f64),
16        // negative fixint
17        0xe0..=0xff => Some((tag as i8) as f64),
18        FLOAT64 => {
19            let bits = read_u64_be(buf, offset + 1)?;
20            Some(f64::from_bits(bits))
21        }
22        FLOAT32 => {
23            let bits = read_u32_be(buf, offset + 1)?;
24            Some(f32::from_bits(bits) as f64)
25        }
26        UINT8 => Some(get(buf, offset + 1)? as f64),
27        UINT16 => Some(read_u16_be(buf, offset + 1)? as f64),
28        UINT32 => Some(read_u32_be(buf, offset + 1)? as f64),
29        UINT64 => Some(read_u64_be(buf, offset + 1)? as f64),
30        INT8 => Some(get(buf, offset + 1)? as i8 as f64),
31        INT16 => Some(read_u16_be(buf, offset + 1)? as i16 as f64),
32        INT32 => Some(read_u32_be(buf, offset + 1)? as i32 as f64),
33        INT64 => Some(read_u64_be(buf, offset + 1)? as i64 as f64),
34        _ => None,
35    }
36}
37
38/// Read an i64 from the value at `offset`. Handles all integer types.
39/// Floats return `None` — use `read_f64` for those. A `uint64` above
40/// `i64::MAX` returns `None` — use `read_integer` for the exact value.
41pub fn read_i64(buf: &[u8], offset: usize) -> Option<i64> {
42    i64::try_from(read_integer(buf, offset)?).ok()
43}
44
45/// Read any msgpack integer at `offset` exactly. `i128` holds every `i64`
46/// and every `u64`. Floats and non-integers return `None`.
47pub fn read_integer(buf: &[u8], offset: usize) -> Option<i128> {
48    let tag = get(buf, offset)?;
49    match tag {
50        0x00..=0x7f => Some(i128::from(tag)),
51        0xe0..=0xff => Some(i128::from(tag as i8)),
52        UINT8 => Some(i128::from(get(buf, offset + 1)?)),
53        UINT16 => Some(i128::from(read_u16_be(buf, offset + 1)?)),
54        UINT32 => Some(i128::from(read_u32_be(buf, offset + 1)?)),
55        UINT64 => Some(i128::from(read_u64_be(buf, offset + 1)?)),
56        INT8 => Some(i128::from(get(buf, offset + 1)? as i8)),
57        INT16 => Some(i128::from(read_u16_be(buf, offset + 1)? as i16)),
58        INT32 => Some(i128::from(read_u32_be(buf, offset + 1)? as i32)),
59        INT64 => Some(i128::from(read_u64_be(buf, offset + 1)? as i64)),
60        _ => None,
61    }
62}
63
64/// Read a msgpack integer or float at `offset` without rounding an integer
65/// through `f64`.
66pub(crate) fn read_numeric(buf: &[u8], offset: usize) -> Option<crate::numeric_cmp::Numeric> {
67    use crate::numeric_cmp::Numeric;
68    match read_integer(buf, offset) {
69        Some(i) => Some(Numeric::Int(i)),
70        None => match get(buf, offset)? {
71            FLOAT32 | FLOAT64 => read_f64(buf, offset).map(Numeric::Float),
72            _ => None,
73        },
74    }
75}
76
77/// Read a string slice from the value at `offset`. Zero-copy — borrows
78/// directly from the input buffer. Returns `None` for non-string types
79/// or invalid UTF-8.
80pub fn read_str(buf: &[u8], offset: usize) -> Option<&str> {
81    let (start, len) = str_bounds(buf, offset)?;
82    let bytes = buf.get(start..start + len)?;
83    str::from_utf8(bytes).ok()
84}
85
86/// Read a string slice at `*off`, advancing `*off` past it. Zero-copy.
87/// Returns `None` for non-string types, invalid UTF-8, or truncated input.
88pub fn read_str_advance<'a>(buf: &'a [u8], off: &mut usize) -> Option<&'a str> {
89    let (start, len) = str_bounds(buf, *off)?;
90    let bytes = buf.get(start..start + len)?;
91    let s = str::from_utf8(bytes).ok()?;
92    *off = start + len;
93    Some(s)
94}
95
96/// Read a `bin` value at `*off`, advancing `*off` past it. Zero-copy —
97/// the returned slice borrows from `buf`. Returns `None` for non-bin tags
98/// or truncated input.
99pub fn read_bin_advance<'a>(buf: &'a [u8], off: &mut usize) -> Option<&'a [u8]> {
100    let tag = get(buf, *off)?;
101    let (len, header) = match tag {
102        BIN8 => (get(buf, *off + 1)? as usize, 2),
103        BIN16 => (read_u16_be(buf, *off + 1)? as usize, 3),
104        BIN32 => (read_u32_be(buf, *off + 1)? as usize, 5),
105        _ => return None,
106    };
107    let start = *off + header;
108    let end = start + len;
109    let data = buf.get(start..end)?;
110    *off = end;
111    Some(data)
112}
113
114/// Read an unsigned integer that fits in a `u32` at `*off`, advancing `*off`
115/// past it. Accepts positive fixint, uint8, uint16, uint32. Returns `None`
116/// for negative, signed-typed, oversized (uint64), or non-integer values.
117pub fn read_u32_advance(buf: &[u8], off: &mut usize) -> Option<u32> {
118    let tag = get(buf, *off)?;
119    match tag {
120        0x00..=0x7f => {
121            *off += 1;
122            Some(tag as u32)
123        }
124        UINT8 => {
125            let v = get(buf, *off + 1)? as u32;
126            *off += 2;
127            Some(v)
128        }
129        UINT16 => {
130            let v = read_u16_be(buf, *off + 1)? as u32;
131            *off += 3;
132            Some(v)
133        }
134        UINT32 => {
135            let v = read_u32_be(buf, *off + 1)?;
136            *off += 5;
137            Some(v)
138        }
139        _ => None,
140    }
141}
142
143/// Return `(data_start, byte_len)` for the string at `offset` without
144/// validating UTF-8. Used internally for key comparison.
145pub(crate) fn str_bounds(buf: &[u8], offset: usize) -> Option<(usize, usize)> {
146    let tag = get(buf, offset)?;
147    match tag {
148        0xa0..=0xbf => {
149            let len = (tag & 0x1f) as usize;
150            Some((offset + 1, len))
151        }
152        STR8 => {
153            let len = get(buf, offset + 1)? as usize;
154            Some((offset + 2, len))
155        }
156        STR16 => {
157            let len = read_u16_be(buf, offset + 1)? as usize;
158            Some((offset + 3, len))
159        }
160        STR32 => {
161            let len = read_u32_be(buf, offset + 1)? as usize;
162            Some((offset + 5, len))
163        }
164        _ => None,
165    }
166}
167
168/// Read a boolean from the value at `offset`.
169pub fn read_bool(buf: &[u8], offset: usize) -> Option<bool> {
170    match get(buf, offset)? {
171        TRUE => Some(true),
172        FALSE => Some(false),
173        _ => None,
174    }
175}
176
177/// Check if the value at `offset` is nil.
178pub fn read_null(buf: &[u8], offset: usize) -> bool {
179    get(buf, offset) == Some(NIL)
180}
181
182/// Return the number of key-value pairs and the offset of the first pair,
183/// for the map starting at `offset`. Returns `None` if not a map.
184pub fn map_header(buf: &[u8], offset: usize) -> Option<(usize, usize)> {
185    let tag = get(buf, offset)?;
186    match tag {
187        0x80..=0x8f => Some(((tag & 0x0f) as usize, offset + 1)),
188        MAP16 => Some((read_u16_be(buf, offset + 1)? as usize, offset + 3)),
189        MAP32 => Some((read_u32_be(buf, offset + 1)? as usize, offset + 5)),
190        _ => None,
191    }
192}
193
194/// Return the number of elements and the offset of the first element,
195/// for the array starting at `offset`. Returns `None` if not an array.
196pub fn array_header(buf: &[u8], offset: usize) -> Option<(usize, usize)> {
197    let tag = get(buf, offset)?;
198    match tag {
199        0x90..=0x9f => Some(((tag & 0x0f) as usize, offset + 1)),
200        ARRAY16 => Some((read_u16_be(buf, offset + 1)? as usize, offset + 3)),
201        ARRAY32 => Some((read_u32_be(buf, offset + 1)? as usize, offset + 5)),
202        _ => None,
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::msgpack_scan::reader::skip_value;
210
211    use serde_json::json;
212
213    /// Helper: encode a serde_json::Value to MessagePack bytes.
214    fn encode(v: &serde_json::Value) -> Vec<u8> {
215        nodedb_types::json_msgpack::json_to_msgpack(v).expect("encode")
216    }
217
218    #[test]
219    fn read_f64_fixint() {
220        assert_eq!(read_f64(&[42u8], 0), Some(42.0));
221    }
222
223    #[test]
224    fn read_f64_negative_fixint() {
225        assert_eq!(read_f64(&[0xffu8], 0), Some(-1.0));
226    }
227
228    #[test]
229    fn read_f64_float64() {
230        let buf = encode(&json!(std::f64::consts::PI));
231        assert_eq!(read_f64(&buf, 0), Some(std::f64::consts::PI));
232    }
233
234    #[test]
235    fn read_f64_uint16() {
236        let buf = encode(&json!(1000));
237        assert_eq!(read_f64(&buf, 0), Some(1000.0));
238    }
239
240    #[test]
241    fn read_f64_float32() {
242        // json! always produces f64, so test float32 with raw bytes
243        // float32 tag (0xca) + 1.5 in IEEE 754 big-endian
244        let buf = [0xca, 0x3f, 0xc0, 0x00, 0x00];
245        let val = read_f64(&buf, 0).unwrap();
246        assert!((val - 1.5).abs() < 1e-6);
247    }
248
249    #[test]
250    fn read_i64_values() {
251        assert_eq!(read_i64(&[42u8], 0), Some(42));
252        assert_eq!(read_i64(&[0xffu8], 0), Some(-1));
253
254        let buf = encode(&json!(300));
255        assert_eq!(read_i64(&buf, 0), Some(300));
256
257        let buf = encode(&json!(-500));
258        assert_eq!(read_i64(&buf, 0), Some(-500));
259    }
260
261    #[test]
262    fn uint64_above_i64_max_never_wraps() {
263        let mut buf = vec![UINT64];
264        buf.extend_from_slice(&u64::MAX.to_be_bytes());
265        assert_eq!(read_i64(&buf, 0), None);
266        assert_eq!(read_integer(&buf, 0), Some(i128::from(u64::MAX)));
267
268        let mut edge = vec![UINT64];
269        edge.extend_from_slice(&(i64::MAX as u64).to_be_bytes());
270        assert_eq!(read_i64(&edge, 0), Some(i64::MAX));
271
272        let mut neg = vec![INT64];
273        neg.extend_from_slice(&i64::MIN.to_be_bytes());
274        assert_eq!(read_i64(&neg, 0), Some(i64::MIN));
275        assert_eq!(read_integer(&neg, 0), Some(i128::from(i64::MIN)));
276    }
277
278    #[test]
279    fn read_numeric_keeps_integers_exact() {
280        use crate::numeric_cmp::Numeric;
281        let buf = encode(&json!(9_007_199_254_740_993_i64));
282        assert!(matches!(
283            read_numeric(&buf, 0),
284            Some(Numeric::Int(9_007_199_254_740_993))
285        ));
286        let buf = encode(&json!(1.5));
287        assert!(matches!(read_numeric(&buf, 0), Some(Numeric::Float(f)) if f == 1.5));
288        let buf = encode(&json!("1"));
289        assert!(read_numeric(&buf, 0).is_none());
290        assert!(read_integer(&buf, 0).is_none());
291    }
292
293    #[test]
294    fn read_str_fixstr() {
295        let buf = encode(&json!("hi"));
296        assert_eq!(read_str(&buf, 0), Some("hi"));
297    }
298
299    #[test]
300    fn read_str_str8() {
301        let long = "a".repeat(40);
302        let buf = encode(&json!(long));
303        assert_eq!(read_str(&buf, 0), Some(long.as_str()));
304    }
305
306    #[test]
307    fn read_bool_values() {
308        assert_eq!(read_bool(&[TRUE], 0), Some(true));
309        assert_eq!(read_bool(&[FALSE], 0), Some(false));
310        assert_eq!(read_bool(&[NIL], 0), None);
311    }
312
313    #[test]
314    fn read_null_check() {
315        assert!(read_null(&[NIL], 0));
316        assert!(!read_null(&[TRUE], 0));
317    }
318
319    #[test]
320    fn map_header_fixmap() {
321        let buf = encode(&json!({"x": 1}));
322        let (count, _data_offset) = map_header(&buf, 0).unwrap();
323        assert_eq!(count, 1);
324    }
325
326    #[test]
327    fn array_header_fixarray() {
328        let buf = encode(&json!([10, 20, 30]));
329        let (count, data_offset) = array_header(&buf, 0).unwrap();
330        assert_eq!(count, 3);
331        assert_eq!(read_i64(&buf, data_offset), Some(10));
332    }
333
334    #[test]
335    fn canonical_integer_smallest_representation() {
336        // fixint (0-127): single byte
337        let buf = encode(&json!(42));
338        assert_eq!(buf.len(), 1);
339        assert_eq!(buf[0], 42);
340
341        // 0 as fixint
342        let buf = encode(&json!(0));
343        assert_eq!(buf.len(), 1);
344        assert_eq!(buf[0], 0);
345
346        // 127 as fixint
347        let buf = encode(&json!(127));
348        assert_eq!(buf.len(), 1);
349        assert_eq!(buf[0], 127);
350
351        // 128 should NOT be fixint. JSON parses as i64, so zerompk uses
352        // int16 (0xd1) since 128 > i8::MAX. This is canonical for signed path.
353        let buf = encode(&json!(128));
354        assert_eq!(buf[0], 0xd1); // int16 tag
355        assert_eq!(buf.len(), 3); // tag + 2 bytes
356
357        // negative fixint (-32 to -1)
358        let buf = encode(&json!(-1));
359        assert_eq!(buf.len(), 1);
360        assert_eq!(buf[0], 0xff); // -1 as negative fixint
361
362        let buf = encode(&json!(-32));
363        assert_eq!(buf.len(), 1);
364        assert_eq!(buf[0], 0xe0); // -32 as negative fixint
365    }
366
367    #[test]
368    fn canonical_map_keys_sorted() {
369        // Keys should be lexicographically sorted in msgpack output.
370        // Encode with keys in non-sorted order in JSON source.
371        let buf = encode(&json!({"z": 1, "a": 2, "m": 3}));
372
373        // Parse map and verify keys come out sorted
374        let (count, mut pos) = map_header(&buf, 0).unwrap();
375        assert_eq!(count, 3);
376
377        let mut keys = Vec::new();
378        for _ in 0..count {
379            let key = read_str(&buf, pos).unwrap();
380            keys.push(key.to_string());
381            pos = skip_value(&buf, pos).unwrap(); // skip key
382            pos = skip_value(&buf, pos).unwrap(); // skip value
383        }
384        assert_eq!(keys, vec!["a", "m", "z"]);
385    }
386
387    #[test]
388    fn canonical_deterministic_bytes() {
389        // Same logical document encoded twice must produce identical bytes.
390        let doc1 = encode(&json!({"name": "alice", "age": 30, "active": true}));
391        let doc2 = encode(&json!({"age": 30, "active": true, "name": "alice"}));
392        assert_eq!(
393            doc1, doc2,
394            "same logical doc must produce identical msgpack bytes"
395        );
396    }
397
398    #[test]
399    fn canonical_nested_map_keys_sorted() {
400        let buf = encode(&json!({"outer": {"z": 1, "a": 2}}));
401        // Extract the inner map
402        let (start, _end) = crate::msgpack_scan::field::extract_field(&buf, 0, "outer").unwrap();
403
404        let (count, mut pos) = map_header(&buf, start).unwrap();
405        assert_eq!(count, 2);
406
407        let key1 = read_str(&buf, pos).unwrap();
408        pos = skip_value(&buf, pos).unwrap();
409        pos = skip_value(&buf, pos).unwrap();
410        let key2 = read_str(&buf, pos).unwrap();
411
412        assert_eq!(key1, "a");
413        assert_eq!(key2, "z");
414    }
415
416    #[test]
417    fn read_bin_advance_all_widths() {
418        // bin8: 0xc4, len=3
419        let mut off = 0;
420        let buf = [BIN8, 3, 0xde, 0xad, 0xbe, 0xff];
421        assert_eq!(
422            read_bin_advance(&buf, &mut off),
423            Some(&[0xde, 0xad, 0xbe][..])
424        );
425        assert_eq!(off, 5);
426
427        // bin16: 0xc5, big-endian len=4
428        let mut off = 0;
429        let buf = [BIN16, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04];
430        assert_eq!(
431            read_bin_advance(&buf, &mut off),
432            Some(&[0x01, 0x02, 0x03, 0x04][..])
433        );
434        assert_eq!(off, 7);
435
436        // bin32: 0xc6, big-endian len=2
437        let mut off = 0;
438        let buf = [BIN32, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xbb];
439        assert_eq!(read_bin_advance(&buf, &mut off), Some(&[0xaa, 0xbb][..]));
440        assert_eq!(off, 7);
441
442        // Non-bin tag returns None and does not advance.
443        let mut off = 0;
444        let buf = [0xc0u8]; // nil
445        assert_eq!(read_bin_advance(&buf, &mut off), None);
446        assert_eq!(off, 0);
447
448        // Truncated returns None.
449        let mut off = 0;
450        let buf = [BIN8, 5, 0x01]; // claims 5 bytes, only 1 present
451        assert_eq!(read_bin_advance(&buf, &mut off), None);
452    }
453
454    #[test]
455    fn read_u32_advance_all_widths() {
456        // positive fixint
457        let mut off = 0;
458        assert_eq!(read_u32_advance(&[42u8], &mut off), Some(42));
459        assert_eq!(off, 1);
460
461        // uint8
462        let mut off = 0;
463        assert_eq!(read_u32_advance(&[UINT8, 200], &mut off), Some(200));
464        assert_eq!(off, 2);
465
466        // uint16
467        let mut off = 0;
468        let buf = [UINT16, 0x12, 0x34];
469        assert_eq!(read_u32_advance(&buf, &mut off), Some(0x1234));
470        assert_eq!(off, 3);
471
472        // uint32
473        let mut off = 0;
474        let buf = [UINT32, 0xde, 0xad, 0xbe, 0xef];
475        assert_eq!(read_u32_advance(&buf, &mut off), Some(0xdeadbeef));
476        assert_eq!(off, 5);
477
478        // negative fixint, int*, uint64, float, etc. all rejected
479        let mut off = 0;
480        assert_eq!(read_u32_advance(&[0xffu8], &mut off), None); // negative fixint
481        assert_eq!(off, 0);
482        let mut off = 0;
483        assert_eq!(read_u32_advance(&[INT8, 5], &mut off), None);
484        let mut off = 0;
485        assert_eq!(
486            read_u32_advance(&[UINT64, 0, 0, 0, 0, 0, 0, 0, 1], &mut off),
487            None
488        );
489
490        // Truncated returns None.
491        let mut off = 0;
492        assert_eq!(read_u32_advance(&[UINT16, 0x12], &mut off), None);
493    }
494
495    #[test]
496    fn read_str_advance_basic() {
497        // fixstr "hi"
498        let mut off = 0;
499        let buf = encode(&json!("hi"));
500        assert_eq!(read_str_advance(&buf, &mut off), Some("hi"));
501        assert_eq!(off, buf.len());
502
503        // Sequential reads
504        let buf = encode(&json!(["one", "two"]));
505        let (count, mut off) = array_header(&buf, 0).unwrap();
506        assert_eq!(count, 2);
507        assert_eq!(read_str_advance(&buf, &mut off), Some("one"));
508        assert_eq!(read_str_advance(&buf, &mut off), Some("two"));
509        assert_eq!(off, buf.len());
510
511        // Non-string returns None.
512        let mut off = 0;
513        assert_eq!(read_str_advance(&[NIL], &mut off), None);
514        assert_eq!(off, 0);
515    }
516}