use std::sync::Arc;
use thiserror::Error;
use crate::{EntityId, TotalF64, Value};
const BOOL: u8 = 0x10;
const LONG: u8 = 0x20;
const DOUBLE: u8 = 0x30;
const INSTANT: u8 = 0x40;
const UUID: u8 = 0x50;
const KEYWORD: u8 = 0x60;
const STR: u8 = 0x70;
const BYTES: u8 = 0x80;
const REF: u8 = 0x90;
#[derive(Debug, Error, Eq, PartialEq)]
pub enum DecodeError {
#[error("truncated sortable value")]
Truncated,
#[error("unknown value tag {0:#x}")]
UnknownTag(u8),
#[error("invalid escaped bytes")]
InvalidEscape,
#[error("invalid UTF-8 string")]
InvalidUtf8,
#[error("trailing bytes after sortable value")]
Trailing,
}
pub trait Encodable {
fn encode_into(&self, out: &mut Vec<u8>);
}
#[must_use]
pub fn encode_value(value: &Value) -> Vec<u8> {
let mut out = Vec::new();
value.encode_into(&mut out);
out
}
impl Encodable for EntityId {
fn encode_into(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.raw().to_be_bytes());
}
}
impl Encodable for u64 {
fn encode_into(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.to_be_bytes());
}
}
impl Encodable for i64 {
fn encode_into(&self, out: &mut Vec<u8>) {
out.extend_from_slice(
&(u64::from_be_bytes(self.to_be_bytes()) ^ (1_u64 << 63)).to_be_bytes(),
);
}
}
impl Encodable for Value {
fn encode_into(&self, out: &mut Vec<u8>) {
match self {
Self::Bool(v) => out.extend_from_slice(&[BOOL, u8::from(*v)]),
Self::Long(v) => {
out.push(LONG);
v.encode_into(out);
}
Self::Double(v) => {
out.push(DOUBLE);
out.extend_from_slice(&v.sortable_bits().to_be_bytes());
}
Self::Instant(v) => {
out.push(INSTANT);
v.encode_into(out);
}
Self::Uuid(v) => {
out.push(UUID);
out.extend_from_slice(&v.to_be_bytes());
}
Self::Keyword(v) => {
out.push(KEYWORD);
v.encode_into(out);
}
Self::Str(v) => {
out.push(STR);
encode_escaped(v.as_bytes(), out);
}
Self::Bytes(v) => {
out.push(BYTES);
encode_escaped(v, out);
}
Self::Ref(v) => {
out.push(REF);
v.encode_into(out);
}
}
}
}
pub fn decode_value(input: &[u8]) -> Result<(Value, usize), DecodeError> {
let Some((&tag, rest)) = input.split_first() else {
return Err(DecodeError::Truncated);
};
let fixed =
|n: usize| -> Result<&[u8], DecodeError> { rest.get(..n).ok_or(DecodeError::Truncated) };
Ok(match tag {
BOOL => (
Value::Bool(*fixed(1)?.first().ok_or(DecodeError::Truncated)? != 0),
2,
),
LONG => (Value::Long(decode_i64(fixed(8)?)), 9),
DOUBLE => (
Value::Double(TotalF64(f64::from_bits(decode_f64_bits(fixed(8)?)))),
9,
),
INSTANT => (Value::Instant(decode_i64(fixed(8)?)), 9),
UUID => (Value::Uuid(u128::from_be_bytes(array_16(fixed(16)?))), 17),
KEYWORD => (Value::Keyword(u64::from_be_bytes(array_8(fixed(8)?))), 9),
REF => (
Value::Ref(EntityId::from_raw(u64::from_be_bytes(array_8(fixed(8)?)))),
9,
),
STR | BYTES => {
let (bytes, used) = decode_escaped(rest)?;
if tag == STR {
(
Value::Str(
std::str::from_utf8(&bytes)
.map_err(|_| DecodeError::InvalidUtf8)?
.into(),
),
used + 1,
)
} else {
(Value::Bytes(Arc::from(bytes)), used + 1)
}
}
other => return Err(DecodeError::UnknownTag(other)),
})
}
fn decode_i64(bytes: &[u8]) -> i64 {
i64::from_be_bytes((u64::from_be_bytes(array_8(bytes)) ^ (1_u64 << 63)).to_be_bytes())
}
fn decode_f64_bits(bytes: &[u8]) -> u64 {
let s = u64::from_be_bytes(array_8(bytes));
if (s & (1_u64 << 63)) == 0 {
!s
} else {
s ^ (1_u64 << 63)
}
}
fn encode_escaped(bytes: &[u8], out: &mut Vec<u8>) {
for b in bytes {
if *b == 0 {
out.extend_from_slice(&[0, 0xff]);
} else {
out.push(*b);
}
}
out.extend_from_slice(&[0, 0]);
}
fn decode_escaped(input: &[u8]) -> Result<(Vec<u8>, usize), DecodeError> {
let mut out = Vec::new();
let mut i = 0;
while i < input.len() {
match input[i] {
0 if input.get(i + 1) == Some(&0) => return Ok((out, i + 2)),
0 if input.get(i + 1) == Some(&0xff) => {
out.push(0);
i += 2;
}
0 => return Err(DecodeError::InvalidEscape),
b => {
out.push(b);
i += 1;
}
}
}
Err(DecodeError::Truncated)
}
fn array_8(bytes: &[u8]) -> [u8; 8] {
let mut out = [0; 8];
out.copy_from_slice(bytes);
out
}
pub fn decode_entity_id(input: &[u8]) -> Result<(EntityId, usize), DecodeError> {
let bytes = input.get(..8).ok_or(DecodeError::Truncated)?;
Ok((EntityId::from_raw(u64::from_be_bytes(array_8(bytes))), 8))
}
pub fn decode_u64(input: &[u8]) -> Result<(u64, usize), DecodeError> {
let bytes = input.get(..8).ok_or(DecodeError::Truncated)?;
Ok((u64::from_be_bytes(array_8(bytes)), 8))
}
fn array_16(bytes: &[u8]) -> [u8; 16] {
let mut out = [0; 16];
out.copy_from_slice(bytes);
out
}