use crate::db::data::structural_field::{
FieldDecodeError,
binary::{
TAG_BYTES, TAG_INT64, TAG_LIST, TAG_NULL, TAG_UINT64, parse_binary_head,
payload_bytes as binary_payload_bytes, push_binary_bytes, push_binary_int64,
push_binary_list_len, push_binary_null, push_binary_uint64, skip_binary_value,
},
storage_key::{decode_storage_key_binary_value_bytes, encode_storage_key_binary_value_bytes},
};
use crate::{
error::InternalError,
model::field::FieldKind,
types::{Date, Decimal, Duration, Int, Nat},
value::Value,
};
use candid::{Int as WrappedInt, Nat as WrappedNat};
use num_bigint::{BigInt, BigUint, Sign as BigIntSign};
pub(super) fn decode_leaf_field_by_kind_bytes(
raw_bytes: &[u8],
kind: FieldKind,
) -> Result<Option<Value>, FieldDecodeError> {
let value = match kind {
FieldKind::Account
| FieldKind::Principal
| FieldKind::Subaccount
| FieldKind::Timestamp
| FieldKind::Unit => decode_storage_key_binary_value_bytes(raw_bytes, kind)?
.expect("storage-key-owned leaf kinds must return a value"),
FieldKind::Date => decode_date_value_bytes(raw_bytes)?,
FieldKind::Decimal { .. } => decode_decimal_value_bytes(raw_bytes)?,
FieldKind::Duration => decode_duration_value_bytes(raw_bytes)?,
FieldKind::IntBig => decode_int_big_value_bytes(raw_bytes)?,
FieldKind::Structured { .. } => decode_structured_leaf_null_value_bytes(raw_bytes)?,
FieldKind::UintBig => decode_uint_big_value_bytes(raw_bytes)?,
FieldKind::Blob
| FieldKind::Bool
| FieldKind::Float32
| FieldKind::Float64
| FieldKind::Int
| FieldKind::Int128
| FieldKind::Text
| FieldKind::Uint
| FieldKind::Uint128
| FieldKind::Ulid => {
return Err(FieldDecodeError::new(
"scalar field unexpectedly bypassed byte-level fast path",
));
}
FieldKind::Enum { .. }
| FieldKind::List(_)
| FieldKind::Map { .. }
| FieldKind::Relation { .. }
| FieldKind::Set(_) => return Ok(None),
};
Ok(Some(value))
}
pub(super) fn encode_leaf_field_binary_bytes(
kind: FieldKind,
value: &Value,
field_name: &str,
) -> Result<Option<Vec<u8>>, InternalError> {
let encoded = match kind {
FieldKind::Account
| FieldKind::Principal
| FieldKind::Subaccount
| FieldKind::Timestamp
| FieldKind::Unit => encode_storage_key_binary_value_bytes(kind, value, field_name)?,
FieldKind::Date => Some(encode_date_value_bytes(value, field_name)?),
FieldKind::Decimal { .. } => Some(encode_decimal_value_bytes(value, field_name)?),
FieldKind::Duration => Some(encode_duration_value_bytes(value, field_name)?),
FieldKind::IntBig => Some(encode_int_big_value_bytes(value, field_name)?),
FieldKind::Structured { .. } => Some(encode_structured_leaf_null_bytes(value, field_name)?),
FieldKind::UintBig => Some(encode_uint_big_value_bytes(value, field_name)?),
FieldKind::Blob
| FieldKind::Bool
| FieldKind::Float32
| FieldKind::Float64
| FieldKind::Int
| FieldKind::Int128
| FieldKind::Text
| FieldKind::Uint
| FieldKind::Uint128
| FieldKind::Ulid
| FieldKind::Enum { .. }
| FieldKind::List(_)
| FieldKind::Map { .. }
| FieldKind::Relation { .. }
| FieldKind::Set(_) => None,
};
Ok(encoded)
}
fn decode_structured_leaf_null_value_bytes(raw_bytes: &[u8]) -> Result<Value, FieldDecodeError> {
decode_required_null_payload(raw_bytes, "structured")?;
Ok(Value::Null)
}
fn encode_structured_leaf_null_bytes(
value: &Value,
field_name: &str,
) -> Result<Vec<u8>, InternalError> {
let Value::Null = value else {
return Err(InternalError::persisted_row_field_encode_failed(
field_name,
"structured ByKind field encoding is unsupported",
));
};
let mut encoded = Vec::new();
push_binary_null(&mut encoded);
Ok(encoded)
}
fn decode_date_value_bytes(raw_bytes: &[u8]) -> Result<Value, FieldDecodeError> {
Date::try_from_i64(decode_required_i64_payload(raw_bytes, "date days")?)
.map(Value::Date)
.ok_or_else(|| FieldDecodeError::new("structural binary: date day count out of range"))
}
fn decode_decimal_value_bytes(raw_bytes: &[u8]) -> Result<Value, FieldDecodeError> {
let items = split_binary_tuple_items(raw_bytes, 2, "decimal")?;
let mantissa_bytes: [u8; 16] = decode_required_bytes_payload(items[0], "decimal mantissa")?
.try_into()
.map_err(|_| {
FieldDecodeError::new(
"structural binary: invalid decimal mantissa length: 16 bytes expected",
)
})?;
let scale = decode_required_u32_payload(items[1], "decimal scale")?;
Ok(Value::Decimal(decode_decimal_mantissa_scale(
i128::from_be_bytes(mantissa_bytes),
scale,
)?))
}
fn decode_duration_value_bytes(raw_bytes: &[u8]) -> Result<Value, FieldDecodeError> {
Ok(Value::Duration(Duration::from_millis(
decode_required_u64_payload(raw_bytes, "duration millis")?,
)))
}
fn decode_int_big_value_bytes(raw_bytes: &[u8]) -> Result<Value, FieldDecodeError> {
let items = split_binary_tuple_items(raw_bytes, 2, "bigint")?;
let sign = decode_bigint_sign_payload(items[0])?;
let magnitude = decode_biguint_payload(items[1])?;
let wrapped = WrappedInt::from(BigInt::from_biguint(sign, magnitude));
Ok(Value::IntBig(Int::from(wrapped)))
}
fn decode_uint_big_value_bytes(raw_bytes: &[u8]) -> Result<Value, FieldDecodeError> {
let wrapped = WrappedNat::from(decode_biguint_payload(raw_bytes)?);
Ok(Value::UintBig(Nat::from(wrapped)))
}
fn encode_date_value_bytes(value: &Value, field_name: &str) -> Result<Vec<u8>, InternalError> {
let Value::Date(value) = value else {
return Err(InternalError::persisted_row_field_encode_failed(
field_name,
format!("field kind Date does not accept runtime value {value:?}"),
));
};
let mut encoded = Vec::new();
push_binary_int64(&mut encoded, i64::from(value.as_days_since_epoch()));
Ok(encoded)
}
fn encode_decimal_value_bytes(value: &Value, field_name: &str) -> Result<Vec<u8>, InternalError> {
let Value::Decimal(value) = value else {
return Err(InternalError::persisted_row_field_encode_failed(
field_name,
format!("field kind Decimal does not accept runtime value {value:?}"),
));
};
let parts = value.parts();
let mut encoded = Vec::new();
push_binary_list_len(&mut encoded, 2);
push_binary_bytes(&mut encoded, &parts.mantissa().to_be_bytes());
push_binary_uint64(&mut encoded, u64::from(parts.scale()));
Ok(encoded)
}
fn encode_duration_value_bytes(value: &Value, field_name: &str) -> Result<Vec<u8>, InternalError> {
let Value::Duration(value) = value else {
return Err(InternalError::persisted_row_field_encode_failed(
field_name,
format!("field kind Duration does not accept runtime value {value:?}"),
));
};
let mut encoded = Vec::new();
push_binary_uint64(&mut encoded, value.as_millis());
Ok(encoded)
}
fn encode_int_big_value_bytes(value: &Value, field_name: &str) -> Result<Vec<u8>, InternalError> {
let Value::IntBig(value) = value else {
return Err(InternalError::persisted_row_field_encode_failed(
field_name,
format!("field kind IntBig does not accept runtime value {value:?}"),
));
};
let (is_negative, digits) = value.sign_and_u32_digits();
let sign = if digits.is_empty() {
0
} else if is_negative {
-1
} else {
1
};
let mut encoded = Vec::new();
push_binary_list_len(&mut encoded, 2);
push_binary_int64(&mut encoded, sign);
push_binary_u32_digit_list(&mut encoded, digits.as_slice());
Ok(encoded)
}
fn encode_uint_big_value_bytes(value: &Value, field_name: &str) -> Result<Vec<u8>, InternalError> {
let Value::UintBig(value) = value else {
return Err(InternalError::persisted_row_field_encode_failed(
field_name,
format!("field kind UintBig does not accept runtime value {value:?}"),
));
};
let mut encoded = Vec::new();
push_binary_u32_digit_list(&mut encoded, value.u32_digits().as_slice());
Ok(encoded)
}
fn push_binary_u32_digit_list(out: &mut Vec<u8>, digits: &[u32]) {
push_binary_list_len(out, digits.len());
for digit in digits {
push_binary_uint64(out, u64::from(*digit));
}
}
fn decode_bigint_sign_payload(raw_bytes: &[u8]) -> Result<BigIntSign, FieldDecodeError> {
match decode_required_i64_payload(raw_bytes, "bigint sign")? {
-1 => Ok(BigIntSign::Minus),
0 => Ok(BigIntSign::NoSign),
1 => Ok(BigIntSign::Plus),
other => Err(FieldDecodeError::new(format!(
"structural binary: invalid bigint sign {other}"
))),
}
}
fn decode_biguint_payload(raw_bytes: &[u8]) -> Result<BigUint, FieldDecodeError> {
let Some((tag, len, payload_start)) = parse_binary_head(raw_bytes, 0)? else {
return Err(FieldDecodeError::new(
"structural binary: truncated biguint payload",
));
};
if tag != TAG_LIST {
return Err(FieldDecodeError::new(
"structural binary: expected biguint limb sequence",
));
}
let mut cursor = payload_start;
let mut limbs = Vec::with_capacity(len as usize);
for _ in 0..len {
let limb_start = cursor;
cursor = skip_binary_value(raw_bytes, cursor)?;
limbs.push(decode_required_u32_payload(
&raw_bytes[limb_start..cursor],
"biguint limb",
)?);
}
if cursor != raw_bytes.len() {
return Err(FieldDecodeError::new(
"structural binary: trailing bytes after biguint payload",
));
}
Ok(BigUint::new(limbs))
}
fn decode_required_null_payload(
raw_bytes: &[u8],
label: &'static str,
) -> Result<(), FieldDecodeError> {
let Some((tag, _, _)) = parse_binary_head(raw_bytes, 0)? else {
return Err(FieldDecodeError::new(format!(
"structural binary: truncated {label} payload"
)));
};
let end = skip_binary_value(raw_bytes, 0)?;
if end != raw_bytes.len() || tag != TAG_NULL {
return Err(FieldDecodeError::new(format!(
"structural binary: expected null for {label}"
)));
}
Ok(())
}
fn decode_required_bytes_payload<'a>(
raw_bytes: &'a [u8],
label: &'static str,
) -> Result<&'a [u8], FieldDecodeError> {
let Some((tag, len, payload_start)) = parse_binary_head(raw_bytes, 0)? else {
return Err(FieldDecodeError::new(format!(
"structural binary: truncated {label} payload"
)));
};
let end = skip_binary_value(raw_bytes, 0)?;
if end != raw_bytes.len() || tag != TAG_BYTES {
return Err(FieldDecodeError::new(format!(
"structural binary: expected bytes for {label}"
)));
}
binary_payload_bytes(raw_bytes, len, payload_start, label)
}
fn decode_required_u32_payload(
raw_bytes: &[u8],
label: &'static str,
) -> Result<u32, FieldDecodeError> {
u32::try_from(decode_required_u64_payload(raw_bytes, label)?)
.map_err(|_| FieldDecodeError::new(format!("structural binary: {label} out of u32 range")))
}
fn decode_required_u64_payload(
raw_bytes: &[u8],
label: &'static str,
) -> Result<u64, FieldDecodeError> {
let Some((tag, len, payload_start)) = parse_binary_head(raw_bytes, 0)? else {
return Err(FieldDecodeError::new(format!(
"structural binary: truncated {label} payload"
)));
};
let end = skip_binary_value(raw_bytes, 0)?;
if end != raw_bytes.len() || tag != TAG_UINT64 || len != 8 {
return Err(FieldDecodeError::new(format!(
"structural binary: expected u64 for {label}"
)));
}
let bytes: [u8; 8] = binary_payload_bytes(raw_bytes, len, payload_start, label)?
.try_into()
.map_err(|_| FieldDecodeError::new(format!("structural binary: invalid {label}")))?;
Ok(u64::from_be_bytes(bytes))
}
fn decode_required_i64_payload(
raw_bytes: &[u8],
label: &'static str,
) -> Result<i64, FieldDecodeError> {
let Some((tag, len, payload_start)) = parse_binary_head(raw_bytes, 0)? else {
return Err(FieldDecodeError::new(format!(
"structural binary: truncated {label} payload"
)));
};
let end = skip_binary_value(raw_bytes, 0)?;
if end != raw_bytes.len() || tag != TAG_INT64 || len != 8 {
return Err(FieldDecodeError::new(format!(
"structural binary: expected i64 for {label}"
)));
}
let bytes: [u8; 8] = binary_payload_bytes(raw_bytes, len, payload_start, label)?
.try_into()
.map_err(|_| FieldDecodeError::new(format!("structural binary: invalid {label}")))?;
Ok(i64::from_be_bytes(bytes))
}
fn split_binary_tuple_items<'a>(
raw_bytes: &'a [u8],
expected_len: u32,
label: &'static str,
) -> Result<Vec<&'a [u8]>, FieldDecodeError> {
let Some((tag, len, payload_start)) = parse_binary_head(raw_bytes, 0)? else {
return Err(FieldDecodeError::new(format!(
"structural binary: truncated {label} payload"
)));
};
if tag != TAG_LIST || len != expected_len {
return Err(FieldDecodeError::new(format!(
"structural binary: expected {label} tuple of length {expected_len}"
)));
}
let mut items = Vec::with_capacity(expected_len as usize);
let mut cursor = payload_start;
for _ in 0..expected_len {
let item_start = cursor;
cursor = skip_binary_value(raw_bytes, cursor)?;
items.push(&raw_bytes[item_start..cursor]);
}
if cursor != raw_bytes.len() {
return Err(FieldDecodeError::new(format!(
"structural binary: trailing bytes after {label} payload"
)));
}
Ok(items)
}
fn decode_decimal_mantissa_scale(mantissa: i128, scale: u32) -> Result<Decimal, FieldDecodeError> {
if scale <= Decimal::max_supported_scale() {
return Ok(Decimal::from_i128_with_scale(mantissa, scale));
}
let mut value = mantissa;
let mut normalized_scale = scale;
while normalized_scale > Decimal::max_supported_scale() {
if value == 0 {
return Ok(Decimal::from_i128_with_scale(
0,
Decimal::max_supported_scale(),
));
}
if value % 10 != 0 {
return Err(FieldDecodeError::new(
"structural binary: invalid decimal payload",
));
}
value /= 10;
normalized_scale -= 1;
}
Ok(Decimal::from_i128_with_scale(value, normalized_scale))
}
#[cfg(test)]
mod tests {
use super::{
TAG_NULL, decode_leaf_field_by_kind_bytes, encode_leaf_field_binary_bytes,
push_binary_bytes, push_binary_int64, push_binary_list_len, push_binary_null,
push_binary_uint64,
};
use crate::{
db::data::structural_field::{
binary::push_binary_text, validate_structural_field_by_kind_bytes,
},
model::field::FieldKind,
types::{Date, Decimal, Duration, Int, Nat},
value::Value,
};
use candid::{Int as WrappedInt, Nat as WrappedNat};
#[test]
fn leaf_field_binary_roundtrips_supported_leaf_wrappers() {
let cases = vec![
(
FieldKind::Date,
Value::Date(Date::new_checked(2025, 10, 19).expect("valid date")),
),
(
FieldKind::Decimal { scale: 2 },
Value::Decimal(Decimal::from_i128_with_scale(12_345, 2)),
),
(FieldKind::Duration, Value::Duration(Duration::from_secs(5))),
(
FieldKind::IntBig,
Value::IntBig(Int::from(WrappedInt::from(123_456_789_i64))),
),
(
FieldKind::UintBig,
Value::UintBig(Nat::from(WrappedNat::from(987_654_321_u64))),
),
(FieldKind::Structured { queryable: false }, Value::Null),
];
for (kind, value) in cases {
let encoded = encode_leaf_field_binary_bytes(kind, &value, "field")
.expect("leaf payload should encode")
.expect("leaf kind should be owned by the leaf lane");
let decoded = decode_leaf_field_by_kind_bytes(encoded.as_slice(), kind)
.expect("leaf payload should decode")
.expect("leaf kind should decode through the leaf lane");
validate_structural_field_by_kind_bytes(encoded.as_slice(), kind)
.expect("leaf payload should validate");
assert_eq!(decoded, value, "leaf roundtrip mismatch for {kind:?}");
}
}
#[test]
fn leaf_field_binary_rejects_malformed_decimal_payload() {
let mut bytes = Vec::new();
push_binary_list_len(&mut bytes, 2);
push_binary_bytes(&mut bytes, &1_i128.to_be_bytes());
push_binary_uint64(&mut bytes, u64::from(Decimal::max_supported_scale() + 1));
let kind = FieldKind::Decimal { scale: 2 };
let decode = decode_leaf_field_by_kind_bytes(bytes.as_slice(), kind);
let validate = validate_structural_field_by_kind_bytes(bytes.as_slice(), kind);
assert!(
decode.is_err(),
"malformed decimal payload must fail decode"
);
assert!(
validate.is_err(),
"malformed decimal payload must fail validate"
);
}
#[test]
fn leaf_field_binary_rejects_invalid_bigint_sign() {
let mut bytes = Vec::new();
push_binary_list_len(&mut bytes, 2);
push_binary_int64(&mut bytes, 2);
push_binary_list_len(&mut bytes, 0);
let decode = decode_leaf_field_by_kind_bytes(bytes.as_slice(), FieldKind::IntBig);
let validate = validate_structural_field_by_kind_bytes(bytes.as_slice(), FieldKind::IntBig);
assert!(decode.is_err(), "invalid bigint sign must fail decode");
assert!(validate.is_err(), "invalid bigint sign must fail validate");
}
#[test]
fn leaf_field_binary_rejects_non_list_biguint_payload() {
let mut bytes = Vec::new();
push_binary_text(&mut bytes, "not-a-limb-list");
let decode = decode_leaf_field_by_kind_bytes(bytes.as_slice(), FieldKind::UintBig);
let validate =
validate_structural_field_by_kind_bytes(bytes.as_slice(), FieldKind::UintBig);
assert!(decode.is_err(), "non-list biguint payload must fail decode");
assert!(
validate.is_err(),
"non-list biguint payload must fail validate"
);
}
#[test]
fn leaf_field_binary_rejects_structured_non_null_payload() {
let mut bytes = Vec::new();
push_binary_null(&mut bytes);
bytes.push(TAG_NULL);
let kind = FieldKind::Structured { queryable: false };
let decode = decode_leaf_field_by_kind_bytes(bytes.as_slice(), kind);
let validate = validate_structural_field_by_kind_bytes(bytes.as_slice(), kind);
assert!(
decode.is_err(),
"structured leaf trailing bytes must fail decode"
);
assert!(
validate.is_err(),
"structured leaf trailing bytes must fail validate"
);
}
}