use crate::etf::tags;
use crate::loader::decode::Literal;
use super::compact::AtomEncoder;
use super::container::EncodeError;
pub(crate) fn encode_literal_chunk(
literals: &[Literal],
atoms: &AtomEncoder<'_>,
) -> Result<Option<Vec<u8>>, EncodeError> {
if literals.is_empty() {
return Ok(None);
}
let mut payload = Vec::new();
payload.extend_from_slice(&u32_len(literals.len())?.to_be_bytes());
for literal in literals {
let mut term = vec![tags::VERSION];
encode_literal(&mut term, literal, atoms)?;
payload.extend_from_slice(&u32_len(term.len())?.to_be_bytes());
payload.extend_from_slice(&term);
}
u32_len(payload.len())?;
let mut chunk = Vec::with_capacity(4 + payload.len());
chunk.extend_from_slice(&0_u32.to_be_bytes());
chunk.extend_from_slice(&payload);
Ok(Some(chunk))
}
fn encode_literal(
out: &mut Vec<u8>,
literal: &Literal,
atoms: &AtomEncoder<'_>,
) -> Result<(), EncodeError> {
match literal {
Literal::Integer(value) => encode_integer(out, *value),
Literal::Float(value) => {
out.push(tags::NEW_FLOAT_EXT);
out.extend_from_slice(&value.to_bits().to_be_bytes());
}
Literal::BigInteger(bytes) => encode_big_integer(out, bytes)?,
Literal::Atom(atom) => encode_atom_name(out, atoms.resolve(*atom)?)?,
Literal::Binary(bytes) => {
out.push(tags::BINARY_EXT);
out.extend_from_slice(&u32_len(bytes.len())?.to_be_bytes());
out.extend_from_slice(bytes);
}
Literal::Tuple(elements) => {
if let Ok(arity) = u8::try_from(elements.len()) {
out.push(tags::SMALL_TUPLE_EXT);
out.push(arity);
} else {
out.push(tags::LARGE_TUPLE_EXT);
out.extend_from_slice(&u32_len(elements.len())?.to_be_bytes());
}
for element in elements {
encode_literal(out, element, atoms)?;
}
}
Literal::Nil => out.push(tags::NIL_EXT),
Literal::List(elements, tail) => {
out.push(tags::LIST_EXT);
out.extend_from_slice(&u32_len(elements.len())?.to_be_bytes());
for element in elements {
encode_literal(out, element, atoms)?;
}
encode_literal(out, tail, atoms)?;
}
Literal::Map(pairs) => {
out.push(tags::MAP_EXT);
out.extend_from_slice(&u32_len(pairs.len())?.to_be_bytes());
for (key, value) in pairs {
encode_literal(out, key, atoms)?;
encode_literal(out, value, atoms)?;
}
}
Literal::String(bytes) => {
let length = u16::try_from(bytes.len()).map_err(|_| EncodeError::ValueOutOfRange)?;
out.push(tags::STRING_EXT);
out.extend_from_slice(&length.to_be_bytes());
out.extend_from_slice(bytes);
}
Literal::ExportFun {
module,
function,
arity,
} => {
out.push(tags::EXPORT_EXT);
encode_atom_name(out, atoms.resolve(*module)?)?;
encode_atom_name(out, atoms.resolve(*function)?)?;
encode_integer(out, i64::from(*arity));
}
}
Ok(())
}
fn encode_integer(out: &mut Vec<u8>, value: i64) {
if let Ok(byte) = u8::try_from(value) {
out.push(tags::SMALL_INTEGER_EXT);
out.push(byte);
} else if let Ok(narrow) = i32::try_from(value) {
out.push(tags::INTEGER_EXT);
out.extend_from_slice(&narrow.to_be_bytes());
} else {
let negative = value.is_negative();
let magnitude = value.unsigned_abs().to_le_bytes();
let trimmed = trim_trailing_zeros(&magnitude);
out.push(tags::SMALL_BIG_EXT);
out.push(trimmed.len() as u8);
out.push(u8::from(negative));
out.extend_from_slice(trimmed);
}
}
fn encode_big_integer(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), EncodeError> {
let (sign, magnitude) = bytes
.split_first()
.ok_or(EncodeError::MalformedBigInteger)?;
if *sign > 1 {
return Err(EncodeError::MalformedBigInteger);
}
if let Ok(length) = u8::try_from(magnitude.len()) {
out.push(tags::SMALL_BIG_EXT);
out.push(length);
} else {
out.push(tags::LARGE_BIG_EXT);
out.extend_from_slice(&u32_len(magnitude.len())?.to_be_bytes());
}
out.push(*sign);
out.extend_from_slice(magnitude);
Ok(())
}
fn encode_atom_name(out: &mut Vec<u8>, name: &str) -> Result<(), EncodeError> {
let bytes = name.as_bytes();
if let Ok(length) = u8::try_from(bytes.len()) {
out.push(tags::SMALL_ATOM_UTF8_EXT);
out.push(length);
} else {
let length = u16::try_from(bytes.len()).map_err(|_| EncodeError::ValueOutOfRange)?;
out.push(tags::ATOM_UTF8_EXT);
out.extend_from_slice(&length.to_be_bytes());
}
out.extend_from_slice(bytes);
Ok(())
}
fn trim_trailing_zeros(bytes: &[u8]) -> &[u8] {
let end = bytes
.iter()
.rposition(|byte| *byte != 0)
.map_or(1, |index| index + 1);
&bytes[..end]
}
fn u32_len(value: usize) -> Result<u32, EncodeError> {
u32::try_from(value).map_err(|_| EncodeError::ValueOutOfRange)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atom::{Atom, AtomTable};
use crate::loader::decode::budget::DecodeBudget;
use crate::loader::decode::decode_literal_chunk;
fn representative_table(table: &AtomTable) -> (Vec<Atom>, Vec<Literal>) {
let ok = table.intern("ok");
let wide = table.intern(&"x".repeat(256));
let lists = table.intern("lists");
let map_fun = table.intern("map");
let atoms = vec![ok, wide, lists, map_fun];
let mut two_pow_63 = vec![0_u8; 9];
two_pow_63[8] = 0x80;
let untrimmed = vec![0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 0];
let mut large_magnitude = vec![1_u8]; large_magnitude.extend(0..=255_u8);
let literals = vec![
Literal::Integer(0),
Literal::Integer(255),
Literal::Integer(-1),
Literal::Integer(256),
Literal::Integer(i64::from(i32::MIN)),
Literal::Integer(i64::from(i32::MAX)),
Literal::Integer(i64::from(i32::MAX) + 1),
Literal::Integer(i64::MIN),
Literal::Float(-1234.5),
Literal::BigInteger(two_pow_63),
Literal::BigInteger(untrimmed),
Literal::BigInteger(large_magnitude),
Literal::Atom(ok),
Literal::Atom(wide),
Literal::Binary(vec![0xDE, 0xAD, 0xBE, 0xEF]),
Literal::Tuple(vec![Literal::Atom(ok), Literal::Integer(7)]),
Literal::Tuple(vec![Literal::Nil; 256]),
Literal::Nil,
Literal::List(
vec![Literal::Integer(1), Literal::Integer(2)],
Box::new(Literal::Integer(3)),
),
Literal::Map(vec![
(Literal::Atom(ok), Literal::Integer(1)),
(Literal::String(b"k".to_vec()), Literal::Nil),
]),
Literal::String(b"golden".to_vec()),
Literal::ExportFun {
module: lists,
function: map_fun,
arity: 2,
},
];
(atoms, literals)
}
const GOLDEN_LITT_CHUNK: &[u8] = &[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x83, 0x61, 0x00,
0x00, 0x00, 0x00, 0x03, 0x83, 0x61, 0xff, 0x00, 0x00, 0x00, 0x06, 0x83, 0x62, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0x06, 0x83, 0x62, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x06, 0x83, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x62, 0x7f, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0x08, 0x83, 0x6e, 0x04, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x0c, 0x83, 0x6e, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x0a, 0x83, 0x46, 0xc0, 0x93, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0c, 0x83, 0x6e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x0d, 0x83, 0x6e, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x00, 0x00, 0x00, 0x01, 0x07, 0x83, 0x6f, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d,
0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00,
0x00, 0x00, 0x05, 0x83, 0x77, 0x02, 0x6f, 0x6b, 0x00, 0x00, 0x01, 0x04, 0x83, 0x76, 0x01,
0x00, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0x00, 0x00, 0x00, 0x0a, 0x83, 0x6d, 0x00, 0x00, 0x00, 0x04, 0xde, 0xad, 0xbe,
0xef, 0x00, 0x00, 0x00, 0x09, 0x83, 0x68, 0x02, 0x77, 0x02, 0x6f, 0x6b, 0x61, 0x07, 0x00,
0x00, 0x01, 0x06, 0x83, 0x69, 0x00, 0x00, 0x01, 0x00, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a,
0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x00, 0x00, 0x00, 0x02, 0x83,
0x6a, 0x00, 0x00, 0x00, 0x0c, 0x83, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x61, 0x01, 0x61, 0x02,
0x61, 0x03, 0x00, 0x00, 0x00, 0x11, 0x83, 0x74, 0x00, 0x00, 0x00, 0x02, 0x77, 0x02, 0x6f,
0x6b, 0x61, 0x01, 0x6b, 0x00, 0x01, 0x6b, 0x6a, 0x00, 0x00, 0x00, 0x0a, 0x83, 0x6b, 0x00,
0x06, 0x67, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x00, 0x00, 0x00, 0x10, 0x83, 0x71, 0x77, 0x05,
0x6c, 0x69, 0x73, 0x74, 0x73, 0x77, 0x03, 0x6d, 0x61, 0x70, 0x61, 0x02,
];
#[test]
fn litt_encode_matches_golden_bytes() {
let table = AtomTable::with_common_atoms();
let (atoms, literals) = representative_table(&table);
let encoder = AtomEncoder::new(&atoms, &table);
let first = encode_literal_chunk(&literals, &encoder)
.expect("representative table encodes")
.expect("non-empty table emits a chunk");
let second = encode_literal_chunk(&literals, &encoder)
.expect("representative table encodes")
.expect("non-empty table emits a chunk");
assert_eq!(first, second, "repeated encodes must be byte-identical");
assert_eq!(first[..4], [0_u8; 4], "chunk must carry the zero prefix");
assert_eq!(
first.len(),
GOLDEN_LITT_CHUNK.len(),
"emitted LitT length diverged from the golden bytes"
);
if let Some(index) = first
.iter()
.zip(GOLDEN_LITT_CHUNK)
.position(|(a, b)| a != b)
{
panic!(
"emitted LitT bytes diverge from golden at byte {index}: \
{:#04x} != {:#04x}",
first[index], GOLDEN_LITT_CHUNK[index]
);
}
}
#[test]
fn litt_golden_bytes_round_trip() {
let table = AtomTable::with_common_atoms();
let (_atoms, literals) = representative_table(&table);
let mut budget = DecodeBudget::new(64, 1 << 16, 1 << 20, 64);
let decoded = decode_literal_chunk(GOLDEN_LITT_CHUNK, &table, &mut budget)
.expect("golden bytes decode as an uncompressed LitT chunk");
assert_eq!(
decoded, literals,
"golden bytes must decode back to the source literal table"
);
}
}