Skip to main content

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}