Skip to main content

corium_core/
encoding.rs

1//! Sortable binary encoding for values and datom key components.
2
3use std::sync::Arc;
4
5use thiserror::Error;
6
7use crate::{EntityId, TotalF64, Value};
8
9const BOOL: u8 = 0x10;
10const LONG: u8 = 0x20;
11const DOUBLE: u8 = 0x30;
12const INSTANT: u8 = 0x40;
13const UUID: u8 = 0x50;
14const KEYWORD: u8 = 0x60;
15const STR: u8 = 0x70;
16const BYTES: u8 = 0x80;
17const REF: u8 = 0x90;
18
19/// Decoding failure.
20#[derive(Debug, Error, Eq, PartialEq)]
21pub enum DecodeError {
22    /// Input ended before a complete value was read.
23    #[error("truncated sortable value")]
24    Truncated,
25    /// Type tag is not known.
26    #[error("unknown value tag {0:#x}")]
27    UnknownTag(u8),
28    /// Escaped byte sequence is invalid.
29    #[error("invalid escaped bytes")]
30    InvalidEscape,
31    /// UTF-8 string payload is invalid.
32    #[error("invalid UTF-8 string")]
33    InvalidUtf8,
34    /// A complete value was followed by unexpected bytes.
35    #[error("trailing bytes after sortable value")]
36    Trailing,
37}
38
39/// Trait for types with Corium sortable encodings.
40pub trait Encodable {
41    /// Appends this value's encoding to `out`.
42    fn encode_into(&self, out: &mut Vec<u8>);
43}
44
45/// Encodes a value into a fresh vector.
46#[must_use]
47pub fn encode_value(value: &Value) -> Vec<u8> {
48    let mut out = Vec::new();
49    value.encode_into(&mut out);
50    out
51}
52
53impl Encodable for EntityId {
54    fn encode_into(&self, out: &mut Vec<u8>) {
55        out.extend_from_slice(&self.raw().to_be_bytes());
56    }
57}
58impl Encodable for u64 {
59    fn encode_into(&self, out: &mut Vec<u8>) {
60        out.extend_from_slice(&self.to_be_bytes());
61    }
62}
63impl Encodable for i64 {
64    fn encode_into(&self, out: &mut Vec<u8>) {
65        out.extend_from_slice(
66            &(u64::from_be_bytes(self.to_be_bytes()) ^ (1_u64 << 63)).to_be_bytes(),
67        );
68    }
69}
70
71impl Encodable for Value {
72    fn encode_into(&self, out: &mut Vec<u8>) {
73        match self {
74            Self::Bool(v) => out.extend_from_slice(&[BOOL, u8::from(*v)]),
75            Self::Long(v) => {
76                out.push(LONG);
77                v.encode_into(out);
78            }
79            Self::Double(v) => {
80                out.push(DOUBLE);
81                out.extend_from_slice(&v.sortable_bits().to_be_bytes());
82            }
83            Self::Instant(v) => {
84                out.push(INSTANT);
85                v.encode_into(out);
86            }
87            Self::Uuid(v) => {
88                out.push(UUID);
89                out.extend_from_slice(&v.to_be_bytes());
90            }
91            Self::Keyword(v) => {
92                out.push(KEYWORD);
93                v.encode_into(out);
94            }
95            Self::Str(v) => {
96                out.push(STR);
97                encode_escaped(v.as_bytes(), out);
98            }
99            Self::Bytes(v) => {
100                out.push(BYTES);
101                encode_escaped(v, out);
102            }
103            Self::Ref(v) => {
104                out.push(REF);
105                v.encode_into(out);
106            }
107        }
108    }
109}
110
111/// Decodes one complete value and returns the value plus bytes consumed.
112///
113/// # Errors
114///
115/// Returns [`DecodeError`] when input is truncated, has an unknown tag, contains
116/// malformed escape sequences, or carries invalid UTF-8 for strings.
117pub fn decode_value(input: &[u8]) -> Result<(Value, usize), DecodeError> {
118    let Some((&tag, rest)) = input.split_first() else {
119        return Err(DecodeError::Truncated);
120    };
121    let fixed =
122        |n: usize| -> Result<&[u8], DecodeError> { rest.get(..n).ok_or(DecodeError::Truncated) };
123    Ok(match tag {
124        BOOL => (
125            Value::Bool(*fixed(1)?.first().ok_or(DecodeError::Truncated)? != 0),
126            2,
127        ),
128        LONG => (Value::Long(decode_i64(fixed(8)?)), 9),
129        DOUBLE => (
130            Value::Double(TotalF64(f64::from_bits(decode_f64_bits(fixed(8)?)))),
131            9,
132        ),
133        INSTANT => (Value::Instant(decode_i64(fixed(8)?)), 9),
134        UUID => (Value::Uuid(u128::from_be_bytes(array_16(fixed(16)?))), 17),
135        KEYWORD => (Value::Keyword(u64::from_be_bytes(array_8(fixed(8)?))), 9),
136        REF => (
137            Value::Ref(EntityId::from_raw(u64::from_be_bytes(array_8(fixed(8)?)))),
138            9,
139        ),
140        STR | BYTES => {
141            let (bytes, used) = decode_escaped(rest)?;
142            if tag == STR {
143                (
144                    Value::Str(
145                        std::str::from_utf8(&bytes)
146                            .map_err(|_| DecodeError::InvalidUtf8)?
147                            .into(),
148                    ),
149                    used + 1,
150                )
151            } else {
152                (Value::Bytes(Arc::from(bytes)), used + 1)
153            }
154        }
155        other => return Err(DecodeError::UnknownTag(other)),
156    })
157}
158fn decode_i64(bytes: &[u8]) -> i64 {
159    i64::from_be_bytes((u64::from_be_bytes(array_8(bytes)) ^ (1_u64 << 63)).to_be_bytes())
160}
161fn decode_f64_bits(bytes: &[u8]) -> u64 {
162    let s = u64::from_be_bytes(array_8(bytes));
163    if (s & (1_u64 << 63)) == 0 {
164        !s
165    } else {
166        s ^ (1_u64 << 63)
167    }
168}
169fn encode_escaped(bytes: &[u8], out: &mut Vec<u8>) {
170    for b in bytes {
171        if *b == 0 {
172            out.extend_from_slice(&[0, 0xff]);
173        } else {
174            out.push(*b);
175        }
176    }
177    out.extend_from_slice(&[0, 0]);
178}
179fn decode_escaped(input: &[u8]) -> Result<(Vec<u8>, usize), DecodeError> {
180    let mut out = Vec::new();
181    let mut i = 0;
182    while i < input.len() {
183        match input[i] {
184            0 if input.get(i + 1) == Some(&0) => return Ok((out, i + 2)),
185            0 if input.get(i + 1) == Some(&0xff) => {
186                out.push(0);
187                i += 2;
188            }
189            0 => return Err(DecodeError::InvalidEscape),
190            b => {
191                out.push(b);
192                i += 1;
193            }
194        }
195    }
196    Err(DecodeError::Truncated)
197}
198
199fn array_8(bytes: &[u8]) -> [u8; 8] {
200    let mut out = [0; 8];
201    out.copy_from_slice(bytes);
202    out
203}
204
205/// Decodes one fixed-width entity-id component.
206///
207/// # Errors
208/// Returns [`DecodeError::Truncated`] when fewer than eight bytes remain.
209pub fn decode_entity_id(input: &[u8]) -> Result<(EntityId, usize), DecodeError> {
210    let bytes = input.get(..8).ok_or(DecodeError::Truncated)?;
211    Ok((EntityId::from_raw(u64::from_be_bytes(array_8(bytes))), 8))
212}
213
214/// Decodes one fixed-width unsigned integer component.
215///
216/// # Errors
217/// Returns [`DecodeError::Truncated`] when fewer than eight bytes remain.
218pub fn decode_u64(input: &[u8]) -> Result<(u64, usize), DecodeError> {
219    let bytes = input.get(..8).ok_or(DecodeError::Truncated)?;
220    Ok((u64::from_be_bytes(array_8(bytes)), 8))
221}
222
223fn array_16(bytes: &[u8]) -> [u8; 16] {
224    let mut out = [0; 16];
225    out.copy_from_slice(bytes);
226    out
227}