Skip to main content

sim_codec_bitwise/
types.rs

1//! Frame value types and wire constants.
2//!
3//! Defines the wire version and flag constants, the 6-bit [`BitwiseTag`]
4//! alphabet, the [`BitwiseFrame`] and [`FrameTables`] carriers, and the
5//! [`DecodeLimits`] bounds that keep decoding fail-closed.
6
7use sim_codec::DecodeLimits as SharedDecodeLimits;
8use sim_kernel::Symbol;
9
10/// The current wire-format version, carried as the first `vbits` of a frame.
11pub(crate) const VERSION: u128 = 1;
12/// No optional frame sections are present.
13pub(crate) const FLAG_NONE: u128 = 0;
14/// The frame carries a single top-level origin (located form).
15pub(crate) const FLAG_ORIGIN: u128 = 1;
16/// The frame carries a per-node origin tree (tree form).
17pub(crate) const FLAG_TREE_ORIGIN: u128 = 2;
18/// The frame body uses dense mode: a repeated, value-equal subtree is written
19/// once and later occurrences are back-references ([`BitwiseTag::Ref`]).
20///
21/// Dense mode is opt-in (see [`crate::encode_dense`]); the plain body, and
22/// therefore [`crate::canonical_bytes`], never sets this bit and never emits a
23/// `Ref`.
24pub(crate) const FLAG_DENSE: u128 = 4;
25/// The set of flag bits this version understands; any other bit is rejected.
26pub(crate) const FLAG_KNOWN: u128 = FLAG_ORIGIN | FLAG_TREE_ORIGIN | FLAG_DENSE;
27
28/// Fail-closed bounds applied while decoding an untrusted bitwise frame.
29///
30/// Every count and length read from a frame is checked against these limits
31/// before any allocation, so a malformed or hostile frame cannot exhaust
32/// memory or recurse without bound. Construct via [`DecodeLimits::default`] or
33/// from the shared [`sim_codec::DecodeLimits`] (see the [`From`] impl).
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct DecodeLimits {
36    /// Maximum total size, in bytes, of the frame accepted by the reader.
37    pub max_frame_bytes: usize,
38    /// Maximum length, in bytes, of any single decoded string.
39    pub max_string_bytes: usize,
40    /// Maximum length, in bytes, of any single decoded byte blob.
41    pub max_blob_bytes: usize,
42    /// Maximum number of entries in any side table or collection.
43    pub max_table_entries: usize,
44    /// Maximum number of `Expr` nodes decoded from one frame.
45    pub max_expr_nodes: usize,
46    /// Maximum nesting depth of the decoded `Expr` graph.
47    pub max_depth: usize,
48    /// Maximum number of trivia items carried by a single origin.
49    pub max_trivia_items: usize,
50}
51
52impl Default for DecodeLimits {
53    fn default() -> Self {
54        SharedDecodeLimits::default().into()
55    }
56}
57
58impl From<SharedDecodeLimits> for DecodeLimits {
59    fn from(shared: SharedDecodeLimits) -> Self {
60        Self {
61            max_frame_bytes: shared.max_input_bytes,
62            max_string_bytes: shared.max_string_bytes,
63            max_blob_bytes: shared.max_blob_bytes,
64            max_table_entries: shared.max_collection_len,
65            max_expr_nodes: shared.max_expr_nodes,
66            max_depth: shared.max_depth,
67            max_trivia_items: shared.max_trivia_items,
68        }
69    }
70}
71
72/// A complete encoded bitwise frame: the self-delimiting header, side tables,
73/// and the bit-packed `Expr` body, owned as a single byte buffer.
74///
75/// The final carrier byte is zero-padded in its unused low bits; decode rejects
76/// any nonzero trailing bit and any trailing whole byte, so the plain-mode
77/// buffer is the smallest canonical byte string for its `Expr` value.
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct BitwiseFrame(
80    /// The raw frame bytes, ready to be written to a byte transport.
81    pub Vec<u8>,
82);
83
84/// The interning side tables carried in a frame header.
85///
86/// Symbols, number-domain symbols, and their namespaces are interned once in
87/// these tables and referenced by index from the body, keeping the frame
88/// compact. Decoding reconstructs the same tables and validates every body
89/// index against them. The collection order matches `sim-codec-binary` so the
90/// two codecs agree on interning.
91#[derive(Clone, Debug, PartialEq, Eq)]
92pub struct FrameTables {
93    /// Interned namespace (lib) strings, referenced by index from symbols.
94    pub libs: Vec<String>,
95    /// Interned symbols referenced by index from the body.
96    pub symbols: Vec<Symbol>,
97    /// Interned number-domain symbols referenced by number-body nodes.
98    pub number_domains: Vec<Symbol>,
99}
100
101/// The fixed-width 6-bit tag that prefixes each node in a frame body.
102///
103/// The first sixteen variants are inline small unsigned integer literals
104/// carrying only a domain index; the next twenty are the structural `Expr`
105/// kinds (one generic tag each -- lengths ride `vbits`); [`BitwiseTag::Ref`] is
106/// emitted only in dense mode (a back-reference to an earlier subtree). Raw
107/// values `37..=63` are reserved and rejected on decode.
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
109#[repr(u8)]
110pub(crate) enum BitwiseTag {
111    UInt0 = 0,
112    UInt1 = 1,
113    UInt2 = 2,
114    UInt3 = 3,
115    UInt4 = 4,
116    UInt5 = 5,
117    UInt6 = 6,
118    UInt7 = 7,
119    UInt8 = 8,
120    UInt9 = 9,
121    UInt10 = 10,
122    UInt11 = 11,
123    UInt12 = 12,
124    UInt13 = 13,
125    UInt14 = 14,
126    UInt15 = 15,
127    Nil = 16,
128    False = 17,
129    True = 18,
130    Number = 19,
131    Symbol = 20,
132    Local = 21,
133    String = 22,
134    Bytes = 23,
135    List = 24,
136    Vector = 25,
137    Map = 26,
138    Set = 27,
139    Call = 28,
140    Infix = 29,
141    Prefix = 30,
142    Postfix = 31,
143    Block = 32,
144    Quote = 33,
145    Annotated = 34,
146    Extension = 35,
147    /// Dense-mode back-reference to an earlier subtree. The plain codec never
148    /// emits it; only [`crate::encode_dense`] does.
149    Ref = 36,
150}
151
152impl BitwiseTag {
153    /// The fixed on-wire width of a tag, in bits.
154    pub(crate) const WIDTH_BITS: usize = 6;
155
156    /// Maps a raw 6-bit value to a tag, or `None` for a reserved/invalid slot.
157    ///
158    /// Written as an exhaustive `match` (the crate forbids `unsafe`), so no
159    /// out-of-range or reserved value can ever transmute into a tag.
160    pub(crate) fn from_u6(value: u8) -> Option<Self> {
161        let tag = match value {
162            0 => Self::UInt0,
163            1 => Self::UInt1,
164            2 => Self::UInt2,
165            3 => Self::UInt3,
166            4 => Self::UInt4,
167            5 => Self::UInt5,
168            6 => Self::UInt6,
169            7 => Self::UInt7,
170            8 => Self::UInt8,
171            9 => Self::UInt9,
172            10 => Self::UInt10,
173            11 => Self::UInt11,
174            12 => Self::UInt12,
175            13 => Self::UInt13,
176            14 => Self::UInt14,
177            15 => Self::UInt15,
178            16 => Self::Nil,
179            17 => Self::False,
180            18 => Self::True,
181            19 => Self::Number,
182            20 => Self::Symbol,
183            21 => Self::Local,
184            22 => Self::String,
185            23 => Self::Bytes,
186            24 => Self::List,
187            25 => Self::Vector,
188            26 => Self::Map,
189            27 => Self::Set,
190            28 => Self::Call,
191            29 => Self::Infix,
192            30 => Self::Prefix,
193            31 => Self::Postfix,
194            32 => Self::Block,
195            33 => Self::Quote,
196            34 => Self::Annotated,
197            35 => Self::Extension,
198            36 => Self::Ref,
199            _ => return None,
200        };
201        Some(tag)
202    }
203
204    /// The inline literal value `0..=15` for a `UInt*` tag, else `None`.
205    pub(crate) fn small_uint(self) -> Option<u8> {
206        let value = self as u8;
207        (value < 16).then_some(value)
208    }
209}