use luna_core::runtime::value::Value;
use luna_core::vm::error::LuaError;
use luna_core::vm::exec::Vm;
const MAX_DEPTH: u32 = 32;
pub(crate) fn cmsgpack_pack(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let mut out = Vec::with_capacity(64);
for i in 0..nargs {
let v = vm.nat_arg(fs, nargs, i);
if let Err(e) = encode_value(vm, &v, &mut out, 0) {
return Err(pack_error(vm, &e));
}
}
let s = vm.heap.intern(&out);
Ok(vm.nat_return(fs, &[Value::Str(s)]))
}
pub(crate) fn cmsgpack_unpack(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let bytes = if nargs >= 1 {
match vm.nat_arg(fs, nargs, 0) {
Value::Str(s) => s.as_bytes().to_vec(),
_ => return Err(unpack_error(vm, "cmsgpack.unpack expects a string argument")),
}
} else {
return Err(unpack_error(vm, "cmsgpack.unpack expects a string argument"));
};
let mut cur = 0usize;
let mut out: Vec<Value> = Vec::new();
while cur < bytes.len() {
match decode_value(vm, &bytes, &mut cur, 0) {
Ok(v) => out.push(v),
Err(e) => return Err(unpack_error(vm, &format!("cmsgpack.unpack: {e}"))),
}
}
Ok(vm.nat_return(fs, &out))
}
#[path = "cmsgpack_encode.rs"]
mod encode;
use encode::encode_value;
fn decode_value(
vm: &mut Vm,
bytes: &[u8],
cur: &mut usize,
depth: u32,
) -> Result<Value, String> {
if depth >= MAX_DEPTH {
return Err("max recursion depth".into());
}
if *cur >= bytes.len() {
return Err("unexpected end of input".into());
}
let tag = bytes[*cur];
*cur += 1;
match tag {
0x00..=0x7f => Ok(Value::Int(tag as i64)),
0x80..=0x8f => {
let n = (tag & 0x0f) as usize;
decode_map(vm, bytes, cur, n, depth + 1)
}
0x90..=0x9f => {
let n = (tag & 0x0f) as usize;
decode_array(vm, bytes, cur, n, depth + 1)
}
0xa0..=0xbf => {
let n = (tag & 0x1f) as usize;
decode_str(vm, bytes, cur, n)
}
0xc0 => Ok(Value::Nil),
0xc1 => Err("reserved msgpack tag 0xc1".into()),
0xc2 => Ok(Value::Bool(false)),
0xc3 => Ok(Value::Bool(true)),
0xc4 => {
let n = read_u8(bytes, cur)? as usize;
decode_str(vm, bytes, cur, n)
}
0xc5 => {
let n = read_u16(bytes, cur)? as usize;
decode_str(vm, bytes, cur, n)
}
0xc6 => {
let n = read_u32(bytes, cur)? as usize;
decode_str(vm, bytes, cur, n)
}
0xca => {
if *cur + 4 > bytes.len() {
return Err("short read float32".into());
}
let f = f32::from_be_bytes([
bytes[*cur],
bytes[*cur + 1],
bytes[*cur + 2],
bytes[*cur + 3],
]);
*cur += 4;
Ok(Value::Float(f as f64))
}
0xcb => {
if *cur + 8 > bytes.len() {
return Err("short read float64".into());
}
let mut a = [0u8; 8];
a.copy_from_slice(&bytes[*cur..*cur + 8]);
*cur += 8;
Ok(Value::Float(f64::from_be_bytes(a)))
}
0xcc => Ok(Value::Int(read_u8(bytes, cur)? as i64)),
0xcd => Ok(Value::Int(read_u16(bytes, cur)? as i64)),
0xce => Ok(Value::Int(read_u32(bytes, cur)? as i64)),
0xcf => {
let n = read_u64(bytes, cur)?;
Ok(Value::Int(n as i64))
}
0xd0 => Ok(Value::Int(read_u8(bytes, cur)? as i8 as i64)),
0xd1 => Ok(Value::Int(read_u16(bytes, cur)? as i16 as i64)),
0xd2 => Ok(Value::Int(read_u32(bytes, cur)? as i32 as i64)),
0xd3 => Ok(Value::Int(read_u64(bytes, cur)? as i64)),
0xd9 => {
let n = read_u8(bytes, cur)? as usize;
decode_str(vm, bytes, cur, n)
}
0xda => {
let n = read_u16(bytes, cur)? as usize;
decode_str(vm, bytes, cur, n)
}
0xdb => {
let n = read_u32(bytes, cur)? as usize;
decode_str(vm, bytes, cur, n)
}
0xdc => {
let n = read_u16(bytes, cur)? as usize;
decode_array(vm, bytes, cur, n, depth + 1)
}
0xdd => {
let n = read_u32(bytes, cur)? as usize;
decode_array(vm, bytes, cur, n, depth + 1)
}
0xde => {
let n = read_u16(bytes, cur)? as usize;
decode_map(vm, bytes, cur, n, depth + 1)
}
0xdf => {
let n = read_u32(bytes, cur)? as usize;
decode_map(vm, bytes, cur, n, depth + 1)
}
0xe0..=0xff => Ok(Value::Int(tag as i8 as i64)),
_ => Err(format!("unsupported msgpack tag 0x{tag:02x}")),
}
}
fn decode_str(vm: &mut Vm, bytes: &[u8], cur: &mut usize, n: usize) -> Result<Value, String> {
if *cur + n > bytes.len() {
return Err("short read str".into());
}
let s = vm.heap.intern(&bytes[*cur..*cur + n]);
*cur += n;
Ok(Value::Str(s))
}
fn elements_fit(n: usize, remaining: usize, per_element: usize) -> usize {
n.min(remaining / per_element + 1)
}
fn decode_array(
vm: &mut Vm,
bytes: &[u8],
cur: &mut usize,
n: usize,
depth: u32,
) -> Result<Value, String> {
let mut entries: Vec<Value> = Vec::with_capacity(elements_fit(n, bytes.len().saturating_sub(*cur), 1));
for _ in 0..n {
entries.push(decode_value(vm, bytes, cur, depth)?);
}
let mut b = vm.new_table();
for (i, v) in entries.into_iter().enumerate() {
b = b.with((i + 1) as i64, v);
}
Ok(Value::Table(b.build()))
}
fn decode_map(
vm: &mut Vm,
bytes: &[u8],
cur: &mut usize,
n: usize,
depth: u32,
) -> Result<Value, String> {
let mut kvs: Vec<(Value, Value)> = Vec::with_capacity(elements_fit(n, bytes.len().saturating_sub(*cur), 2));
for _ in 0..n {
let k = decode_value(vm, bytes, cur, depth)?;
let v = decode_value(vm, bytes, cur, depth)?;
kvs.push((k, v));
}
let mut b = vm.new_table();
for (k, v) in kvs {
b = b.with(k, v);
}
Ok(Value::Table(b.build()))
}
fn read_u8(bytes: &[u8], cur: &mut usize) -> Result<u8, String> {
if *cur >= bytes.len() {
return Err("short read u8".into());
}
let n = bytes[*cur];
*cur += 1;
Ok(n)
}
fn read_u16(bytes: &[u8], cur: &mut usize) -> Result<u16, String> {
if *cur + 2 > bytes.len() {
return Err("short read u16".into());
}
let n = u16::from_be_bytes([bytes[*cur], bytes[*cur + 1]]);
*cur += 2;
Ok(n)
}
fn read_u32(bytes: &[u8], cur: &mut usize) -> Result<u32, String> {
if *cur + 4 > bytes.len() {
return Err("short read u32".into());
}
let n = u32::from_be_bytes([bytes[*cur], bytes[*cur + 1], bytes[*cur + 2], bytes[*cur + 3]]);
*cur += 4;
Ok(n)
}
fn read_u64(bytes: &[u8], cur: &mut usize) -> Result<u64, String> {
if *cur + 8 > bytes.len() {
return Err("short read u64".into());
}
let mut a = [0u8; 8];
a.copy_from_slice(&bytes[*cur..*cur + 8]);
*cur += 8;
Ok(u64::from_be_bytes(a))
}
fn pack_error(vm: &mut Vm, msg: &str) -> LuaError {
let s = vm.heap.intern(format!("cmsgpack.pack: {msg}").as_bytes());
LuaError::new(Value::Str(s))
}
fn unpack_error(vm: &mut Vm, msg: &str) -> LuaError {
let s = vm.heap.intern(msg.as_bytes());
LuaError::new(Value::Str(s))
}
pub(crate) fn install_cmsgpack(vm: &mut Vm) {
let pack_fn = vm.native(cmsgpack_pack);
let unpack_fn = vm.native(cmsgpack_unpack);
let t = vm.table_of([("pack", pack_fn), ("unpack", unpack_fn)]);
let _ = vm.set_global("cmsgpack", Value::Table(t));
}
#[cfg(test)]
#[path = "cmsgpack_tests.rs"]
mod bound_tests;