#[cfg(not(feature = "std"))]
use crate::nostd_prelude::*;
use crate::value::{COLD_TAG_HASH, COLD_TAG_STRING, HashData, SmallBytes, Value};
const PACKED_FLAG: u32 = 1 << 31;
use alloc::sync::Arc;
pub(crate) fn encode(v: &Value) -> Option<(Vec<u8>, u8)> {
match v {
Value::ArcBulk(a) => Some((a.as_ref().to_vec(), COLD_TAG_STRING)),
Value::Hash(h) => {
let mut out = Vec::with_capacity(4 + h.len() * 16);
out.extend_from_slice(&(h.len() as u32).to_le_bytes());
for (f, val) in h.iter() {
put_chunk(&mut out, f.as_slice());
put_chunk(&mut out, val.as_slice());
}
Some((out, COLD_TAG_HASH))
}
Value::PackedRow(r) => {
let mut out = Vec::with_capacity(4 + r.len() * 16);
out.extend_from_slice(&((r.len() as u32) | PACKED_FLAG).to_le_bytes());
for (f, val) in r.fields() {
put_chunk(&mut out, f);
put_chunk(&mut out, val);
}
Some((out, COLD_TAG_HASH))
}
Value::SmallHashInline(h) => {
let mut out = Vec::with_capacity(4 + 22 + 8);
out.extend_from_slice(&(h.len() as u32).to_le_bytes());
for (f, val) in h.iter() {
put_chunk(&mut out, f);
put_chunk(&mut out, val);
}
Some((out, COLD_TAG_HASH))
}
_ => None,
}
}
#[inline]
fn put_chunk(out: &mut Vec<u8>, bytes: &[u8]) {
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(bytes);
}
pub(crate) fn decode(tag: u8, payload: Vec<u8>) -> Result<Value, &'static str> {
match tag {
COLD_TAG_STRING => {
Ok(crate::string_set::pick_value_for_set_owned(payload))
}
COLD_TAG_HASH => decode_hash(&payload),
_ => Err("tier: unknown cold type tag"),
}
}
pub(crate) fn pairs_fit(n: usize, payload_len: usize) -> usize {
n.min(payload_len / 8 + 1)
}
fn decode_hash(p: &[u8]) -> Result<Value, &'static str> {
let mut cur = 0usize;
let raw = read_u32(p, &mut cur)?;
let n = (raw & !PACKED_FLAG) as usize;
let mut pairs = Vec::with_capacity(pairs_fit(n, p.len()));
for _ in 0..n {
let f = read_chunk(p, &mut cur)?;
let v = read_chunk(p, &mut cur)?;
pairs.push((f, v));
}
if cur != p.len() {
return Err("tier: hash payload has trailing bytes");
}
if raw & PACKED_FLAG != 0
&& let Some(r) = rebuild_packed(&pairs)
{
return Ok(Value::PackedRow(r));
}
let mut h = HashData::with_capacity(n.max(1));
for (f, v) in pairs {
h.insert(SmallBytes::from_slice(f), SmallBytes::from_slice(v));
}
Ok(Value::Hash(Arc::new(h)))
}
fn rebuild_packed(pairs: &[(&[u8], &[u8])]) -> Option<crate::packed_row::PackedRow> {
let names: crate::packed_row::ColumnNames = pairs.iter().map(|(f, _)| f.to_vec()).collect();
let vals: Vec<Option<&[u8]>> = pairs.iter().map(|(_, v)| Some(*v)).collect();
crate::packed_row::PackedRow::build(&names, &vals)
}
fn read_u32(p: &[u8], cur: &mut usize) -> Result<u32, &'static str> {
let end = cur.checked_add(4).ok_or("tier: offset overflow")?;
let b: [u8; 4] =
p.get(*cur..end).ok_or("tier: truncated length")?.try_into().expect("4-byte slice");
*cur = end;
Ok(u32::from_le_bytes(b))
}
fn read_chunk<'a>(p: &'a [u8], cur: &mut usize) -> Result<&'a [u8], &'static str> {
let len = read_u32(p, cur)? as usize;
let end = cur.checked_add(len).ok_or("tier: offset overflow")?;
let out = p.get(*cur..end).ok_or("tier: truncated chunk")?;
*cur = end;
Ok(out)
}