yo_doc/head.rs
1//! The four byte header that every value begins with, at every level.
2//!
3//! One word says what a value is and how big it is, and for a container it is
4//! also the only thing a reader needs before it can index the entry table. It
5//! is read unaligned, because a document is stored inside a record and a record
6//! starts wherever the log put it.
7
8/// What a value is, as a caller sees it.
9///
10/// The wire keeps object and array apart with a bit rather than a kind, since
11/// they share a layout, but nobody outside this crate wants to write
12/// `kind == Container && is_array()` so the two are separate here.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum Kind {
15 /// `null`.
16 Null,
17 /// `true` or `false`.
18 Bool,
19 /// A signed 64 bit integer.
20 Int,
21 /// A 64 bit float.
22 Float,
23 /// A UTF-8 string.
24 Text,
25 /// An ordered list of values.
26 Array,
27 /// A set of keys, each with a value.
28 Object,
29}
30
31/// The wire tag, which is what the low three bits of a header hold.
32///
33/// The numbers are the format, so they are written out rather than derived from
34/// declaration order, and 2 is missing on purpose: false and true are 1 and 3
35/// so that the low bit of a boolean is the boolean.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[repr(u8)]
38pub(crate) enum Tag {
39 Null = 0,
40 False = 1,
41 True = 3,
42 Int = 4,
43 Float = 5,
44 Text = 6,
45 Container = 7,
46}
47
48impl Tag {
49 /// The tag the low three bits of `head` name, or `None` for the one value
50 /// in that range this version does not define.
51 pub(crate) fn of(head: u32) -> Option<Tag> {
52 match head & 0b111 {
53 0 => Some(Tag::Null),
54 1 => Some(Tag::False),
55 3 => Some(Tag::True),
56 4 => Some(Tag::Int),
57 5 => Some(Tag::Float),
58 6 => Some(Tag::Text),
59 7 => Some(Tag::Container),
60 _ => None,
61 }
62 }
63}
64
65/// Set on a container that is an array, clear on one that is an object.
66pub(crate) const ARRAY: u32 = 1 << 3;
67
68/// Set on an object whose members are in key order, which is every object this
69/// version writes. A reader that finds it clear falls back to a linear scan
70/// rather than refusing the document, because an object out of order is still
71/// readable and only a lookup gets slower.
72pub(crate) const SORTED: u32 = 1 << 4;
73
74/// Set on a container whose entry table carries offsets.
75///
76/// Every container this version writes has one, and a reader requires it. It is
77/// here so that a later version can store lengths instead for a small container
78/// and say so, which is the kind of change the format freeze has to leave room
79/// for.
80pub(crate) const OFFSETS: u32 = 1 << 5;
81
82/// Set on an object whose keys are two byte ids from the collection's intern
83/// table rather than bytes in a key region.
84pub(crate) const INTERNED: u32 = 1 << 6;
85
86/// Where the count starts.
87pub(crate) const COUNT_SHIFT: u32 = 8;
88
89/// The largest count a header can hold, which caps a container at 16.7 M
90/// elements and a scalar at 16 MiB.
91pub const COUNT_MAX: usize = (1 << 24) - 1;
92
93/// How deep a document may nest.
94///
95/// A reader walks a document with recursion, down the right hand edge to find a
96/// length and down everything to check one, so the depth a writer will produce
97/// and the depth a reader will accept have to be one number. It is the same
98/// limit RedisJSON has, which means a document that was legal there stays legal
99/// here.
100pub const DEPTH_MAX: usize = 128;
101
102/// A header built from its parts.
103pub(crate) fn head(tag: Tag, flags: u32, count: usize) -> u32 {
104 debug_assert!(count <= COUNT_MAX, "the caller checked the count");
105 (tag as u32) | flags | ((count as u32) << COUNT_SHIFT)
106}
107
108/// The count a header carries: how many elements a container holds, and how
109/// many bytes of payload a scalar has.
110pub(crate) fn count(head: u32) -> usize {
111 (head >> COUNT_SHIFT) as usize
112}
113
114/// The four bytes at `at`, or `None` if they are not all there.
115pub(crate) fn read(b: &[u8], at: usize) -> Option<u32> {
116 let end = at.checked_add(4)?;
117 let raw = b.get(at..end)?;
118 Some(u32::from_le_bytes(raw.try_into().expect("four bytes")))
119}