passdata/
values.rs

1//! Values are stored in a `Context` and are indirectly referenced by various
2//! identifier types. Values are immutable.
3//!
4//! # Limitations
5//!
6//! Values are not constructed during a program's execution. For instance,
7//! strings are not concatenated. Values have to be added explicitly to the
8//! context before execution via facts or rules.
9//!
10//! Due to the limitations on a HTTP cookie/header value's length, there is an
11//! absolute limit to the amount of data that can be encoded.
12
13#[cfg(all(feature = "alloc", not(feature = "std")))]
14use alloc::borrow::Cow;
15use core::fmt;
16#[cfg(feature = "std")]
17use std::{borrow::Cow, error};
18
19use crate::{
20    error::{Error, ErrorKind},
21    ConstantTy, Section, SectionMut,
22};
23
24const BOOL_FALSE_INDEX: u16 = 0;
25const BOOL_TRUE_INDEX: u16 = 1;
26
27const MAX_DATA_LEN: usize = 4096;
28const BYTES_TY_BITMASK: u16 = 3 << 13;
29const NUM_TY_BITMASK: u16 = 2 << 13;
30
31/// An interned bytes reference.
32#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
33pub(crate) struct BytesId(pub(crate) u16);
34
35/// An interned number reference.
36#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
37pub(crate) struct NumId(pub(crate) u16);
38
39/// A scalar reference.
40#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
41pub(crate) struct ScalarId(u16);
42
43impl From<bool> for ScalarId {
44    fn from(value: bool) -> Self {
45        if value {
46            Self(1)
47        } else {
48            Self(0)
49        }
50    }
51}
52
53impl From<NumId> for ScalarId {
54    fn from(value: NumId) -> Self {
55        Self(value.0)
56    }
57}
58
59impl From<BytesId> for ScalarId {
60    fn from(value: BytesId) -> Self {
61        Self(value.0)
62    }
63}
64
65/// A constant reference
66#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
67pub struct ConstantId(pub(crate) u16);
68
69impl From<bool> for ConstantId {
70    fn from(value: bool) -> Self {
71        Self(ScalarId::from(value).0)
72    }
73}
74
75impl From<NumId> for ConstantId {
76    fn from(value: NumId) -> Self {
77        Self(value.0)
78    }
79}
80
81impl From<BytesId> for ConstantId {
82    fn from(value: BytesId) -> Self {
83        Self(value.0)
84    }
85}
86
87impl From<ScalarId> for ConstantId {
88    fn from(value: ScalarId) -> Self {
89        Self(value.0)
90    }
91}
92
93/// A constant value.
94#[derive(Debug, Clone, Hash, PartialEq, Eq)]
95pub enum Constant<'a> {
96    /// Boolean
97    Bool(bool),
98    /// Number
99    Num(i64),
100    /// Byte string
101    Bytes(&'a [u8]),
102}
103
104impl<'a> Default for Constant<'a> {
105    fn default() -> Self {
106        Self::Bool(false)
107    }
108}
109
110impl<'a> From<bool> for Constant<'a> {
111    fn from(value: bool) -> Self {
112        Constant::Bool(value)
113    }
114}
115
116impl<'a> From<i64> for Constant<'a> {
117    fn from(value: i64) -> Self {
118        Constant::Num(value)
119    }
120}
121
122impl<'a> From<&'a str> for Constant<'a> {
123    fn from(value: &'a str) -> Self {
124        Constant::Bytes(value.as_bytes())
125    }
126}
127
128impl<'a> From<&'a [u8]> for Constant<'a> {
129    fn from(value: &'a [u8]) -> Self {
130        Constant::Bytes(value)
131    }
132}
133
134/// Cannot convert constant value to type.
135#[derive(Clone, Copy)]
136pub struct InvalidType;
137
138#[cfg(feature = "std")]
139impl error::Error for InvalidType {
140    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
141        None
142    }
143}
144
145impl fmt::Display for InvalidType {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        f.write_str("invalid type")
148    }
149}
150
151impl fmt::Debug for InvalidType {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        f.write_str("invalid type")
154    }
155}
156
157impl<'a> TryFrom<Constant<'a>> for &'a [u8] {
158    type Error = InvalidType;
159
160    fn try_from(value: Constant<'a>) -> Result<Self, Self::Error> {
161        match value {
162            Constant::Bool(_) | Constant::Num(_) => Err(InvalidType),
163            Constant::Bytes(bytes) => Ok(bytes),
164        }
165    }
166}
167
168impl<'a> TryFrom<Constant<'a>> for Cow<'a, [u8]> {
169    type Error = InvalidType;
170
171    fn try_from(value: Constant<'a>) -> Result<Self, Self::Error> {
172        match value {
173            Constant::Bool(_) | Constant::Num(_) => Err(InvalidType),
174            Constant::Bytes(bytes) => Ok(Cow::from(bytes)),
175        }
176    }
177}
178
179impl<'a> TryFrom<Constant<'a>> for &'a str {
180    type Error = InvalidType;
181
182    fn try_from(value: Constant<'a>) -> Result<Self, Self::Error> {
183        match value {
184            Constant::Bool(_) | Constant::Num(_) => Err(InvalidType),
185            Constant::Bytes(bytes) => Ok(core::str::from_utf8(bytes).map_err(|_| InvalidType)?),
186        }
187    }
188}
189
190impl<'a> TryFrom<Constant<'a>> for Cow<'a, str> {
191    type Error = InvalidType;
192
193    fn try_from(value: Constant<'a>) -> Result<Self, Self::Error> {
194        match value {
195            Constant::Bool(_) | Constant::Num(_) => Err(InvalidType),
196            Constant::Bytes(bytes) => Ok(Cow::Borrowed(
197                core::str::from_utf8(bytes).map_err(|_| InvalidType)?,
198            )),
199        }
200    }
201}
202
203impl<'a> TryFrom<Constant<'a>> for i64 {
204    type Error = InvalidType;
205
206    fn try_from(value: Constant<'a>) -> Result<Self, Self::Error> {
207        match value {
208            Constant::Bool(_) | Constant::Bytes(_) => Err(InvalidType),
209            Constant::Num(n) => Ok(n),
210        }
211    }
212}
213
214impl<'a> TryFrom<Constant<'a>> for bool {
215    type Error = InvalidType;
216
217    fn try_from(value: Constant<'a>) -> Result<Self, Self::Error> {
218        match value {
219            Constant::Bool(b) => Ok(b),
220            Constant::Num(_) | Constant::Bytes(_) => Err(InvalidType),
221        }
222    }
223}
224
225#[derive(Debug)]
226pub(crate) struct Iter<'a> {
227    data: &'a [u8],
228}
229
230fn iter<'a>(ctx: &Section<'a>) -> Iter<'a> {
231    let data = ctx.data();
232    if data.is_empty() {
233        return Iter { data: &[] };
234    }
235    Iter { data: &data[2..] }
236}
237
238impl<'a> Iterator for Iter<'a> {
239    type Item = (ConstantTy, &'a [u8]);
240
241    fn next(&mut self) -> Option<Self::Item> {
242        if self.data.is_empty() {
243            return None;
244        }
245
246        let first_len_byte = self.data[0];
247        let ty = match first_len_byte >> 5 {
248            2 => ConstantTy::Num,
249            3 => ConstantTy::Bytes,
250            _ => unreachable!(),
251        };
252
253        let first_len_byte = first_len_byte & 0x1F;
254        let len = u16::from_be_bytes([first_len_byte, self.data[1]]);
255
256        let len = usize::from(len);
257        let slice = &self.data[2..2 + len];
258        self.data = &self.data[2 + len..];
259
260        Some((ty, slice))
261    }
262}
263
264const SECTION_INIT: [u8; 5] = [1, 0, 2, 0, 0];
265
266#[must_use]
267#[inline]
268fn elem_len(section: &SectionMut<'_>) -> u16 {
269    u16::from_be_bytes([
270        section.data[section.start + 3],
271        section.data[section.start + 4],
272    ])
273}
274
275#[inline]
276fn set_elem_len(section: &mut SectionMut<'_>, len: u16) {
277    let len_bytes = len.to_be_bytes();
278    section.data[section.start + 3] = len_bytes[0];
279    section.data[section.start + 4] = len_bytes[1];
280}
281
282#[must_use]
283pub(crate) fn bytes_id(ctx: &Section<'_>, needle: &[u8]) -> Option<BytesId> {
284    iter(ctx)
285        .position(|(ty, value)| ty == ConstantTy::Bytes && value == needle)
286        .map(|pos| {
287            BytesId(2 + u16::try_from(pos).expect("greater than u16::MAX byte slices in context"))
288        })
289}
290
291/// Returns the slice of bytes given the [`BytesId`].
292#[must_use]
293pub(crate) fn bytes<'a>(ctx: &Section<'a>, id: BytesId) -> &'a [u8] {
294    let idx = usize::from(id.0 - 2);
295    let Some((ty, value)) = iter(ctx).nth(idx) else {
296        unreachable!()
297    };
298    assert_eq!(ty, ConstantTy::Bytes);
299    value
300}
301
302/// Given a slice of bytes, returns a [`BytesId`].
303///
304/// If the slice of bytes value already exists in the context, it will return the
305/// existing assigned [`BytesId`]. If the slice of bytes value does not exist in
306/// the context, a unique [`BytesId`] will be assigned and returned.
307pub(crate) fn get_or_insert_bytes_id(
308    ctx: &mut SectionMut<'_>,
309    needle: &[u8],
310) -> Result<BytesId, Error> {
311    if needle.len() > MAX_DATA_LEN {
312        return Err(Error::with_kind(ErrorKind::InvalidContextValue));
313    }
314
315    if let Some(bytes_id) = bytes_id(&ctx.as_ref(), needle) {
316        Ok(bytes_id)
317    } else {
318        ctx.init(&SECTION_INIT);
319
320        let len = elem_len(ctx);
321        if len >= u16::MAX - 2 {
322            return Err(Error::with_kind(ErrorKind::ContextFull));
323        }
324        let len = len + 1;
325
326        let needle_len = needle.len();
327        assert!(needle_len <= 4096);
328        let needle_len_with_ty = u16::try_from(needle_len).unwrap() | BYTES_TY_BITMASK;
329        ctx.splice(needle_len_with_ty.to_be_bytes());
330        ctx.splice(needle.iter().copied());
331
332        set_elem_len(ctx, len);
333        ctx.update_len();
334
335        Ok(BytesId(2 + len - 1))
336    }
337}
338
339/// Given a `i64` value, returns the existing [`NumId`], if the `i64` exists in the context.
340#[must_use]
341fn num_id(ctx: &Section<'_>, needle: i64) -> Option<NumId> {
342    iter(ctx)
343        .position(|(ty, value)| {
344            ty == ConstantTy::Num
345                && i64::from_be_bytes(<[u8; 8]>::try_from(value).unwrap()) == needle
346        })
347        .map(|pos| NumId(2 + u16::try_from(pos).expect("greater than u16::MAX numbers in context")))
348}
349
350/// Returns the number value given the [`NumId`].
351#[must_use]
352#[allow(unused)]
353fn num(ctx: &Section<'_>, id: NumId) -> i64 {
354    let idx = usize::from(id.0 - 2);
355    let Some((ty, value)) = iter(ctx).nth(idx) else {
356        unreachable!()
357    };
358    assert_eq!(ty, ConstantTy::Num);
359    i64::from_be_bytes(<[u8; 8]>::try_from(value).unwrap())
360}
361
362/// Given a number, returns a [`NumId`].
363///
364/// If the number value already exists in the context, it will return the
365/// existing assigned [`NumId`]. If the number value does not exist in
366/// the context, a unique [`NumId`] will be assigned and returned.
367pub(crate) fn get_or_insert_num_id(ctx: &mut SectionMut<'_>, needle: i64) -> Result<NumId, Error> {
368    if let Some(id) = num_id(&ctx.as_ref(), needle) {
369        Ok(id)
370    } else {
371        ctx.init(&SECTION_INIT);
372
373        let len = elem_len(ctx);
374        if len >= u16::MAX - 2 {
375            return Err(Error::with_kind(ErrorKind::ContextFull));
376        }
377        let len = len + 1;
378
379        let needle_len = 8u16;
380        let needle_len = needle_len | NUM_TY_BITMASK;
381        ctx.splice(needle_len.to_be_bytes());
382        ctx.splice(needle.to_be_bytes());
383
384        set_elem_len(ctx, len);
385        ctx.update_len();
386
387        Ok(NumId(2 + len - 1))
388    }
389}
390
391/// Returns the value given the context.
392pub(crate) fn constant<'a, T>(ctx: &Section<'a>, id: T) -> Constant<'a>
393where
394    ConstantId: From<T>,
395{
396    let id = ConstantId::from(id);
397
398    if id.0 == BOOL_FALSE_INDEX {
399        return Constant::Bool(false);
400    }
401
402    if id.0 == BOOL_TRUE_INDEX {
403        return Constant::Bool(true);
404    }
405
406    let Some((ty, value)) = iter(ctx).nth(usize::from(id.0 - 2)) else {
407        unreachable!()
408    };
409
410    match ty {
411        ConstantTy::Unknown | ConstantTy::Bool => unreachable!(),
412        ConstantTy::Num => Constant::Num(i64::from_be_bytes(<[u8; 8]>::try_from(value).unwrap())),
413        ConstantTy::Bytes => Constant::Bytes(value),
414    }
415}