graphite_binary 0.1.0

Efficient binary serialization for the graphite_minecraft project
Documentation
use std::borrow::Cow;

use super::*;
use crate::slice_serialization::{BigEndian, Single, SliceSerializable};
use anyhow::bail;

pub fn read(bytes: &mut &[u8]) -> anyhow::Result<NBT> {
    let type_id: u8 = Single::read(bytes)?;
    if type_id == TAG_END_ID.0 {
        return Ok(NBT::new());
    } else if type_id != TAG_COMPOUND_ID.0 {
        bail!("nbt_decode: root must be a compound");
    }

    let mut nodes = Vec::new();
    let name = read_string(bytes)?;
    let children = read_compound(bytes, &mut nodes)?;

    Ok(NBT {
        root_name: name.into_owned(),
        root_children: children,
        nodes,
    })
}

fn read_node(bytes: &mut &[u8], nodes: &mut Vec<NBTNode>, type_id: u8) -> anyhow::Result<usize> {
    debug_assert!(
        type_id != TAG_END_ID.0,
        "read_node must not be called with TAG_END"
    );

    let node = match TagType(type_id) {
        TAG_BYTE_ID => NBTNode::Byte(Single::read(bytes)?),
        TAG_SHORT_ID => NBTNode::Short(BigEndian::read(bytes)?),
        TAG_INT_ID => NBTNode::Int(BigEndian::read(bytes)?),
        TAG_LONG_ID => NBTNode::Long(BigEndian::read(bytes)?),
        TAG_FLOAT_ID => NBTNode::Float(BigEndian::read(bytes)?),
        TAG_DOUBLE_ID => NBTNode::Double(BigEndian::read(bytes)?),
        TAG_BYTE_ARRAY_ID => NBTNode::ByteArray(read_byte_array(bytes)?),
        TAG_STRING_ID => NBTNode::String(read_string(bytes)?.into_owned()),
        TAG_LIST_ID => {
            let (type_id, children) = read_list(bytes, nodes)?;
            NBTNode::List { type_id: TagType(type_id), children }
        }
        TAG_COMPOUND_ID => NBTNode::Compound(read_compound(bytes, nodes)?),
        TAG_INT_ARRAY_ID => NBTNode::IntArray(read_int_array(bytes)?),
        TAG_LONG_ARRAY_ID => NBTNode::LongArray(read_long_array(bytes)?),
        _ => bail!("unknown type id: {}", type_id),
    };
    nodes.push(node);
    Ok(nodes.len() - 1)
}

fn read_compound(bytes: &mut &[u8], nodes: &mut Vec<NBTNode>) -> anyhow::Result<NBTCompound> {
    let mut children = NBTCompound(Vec::new());

    loop {
        let type_id: u8 = Single::read(bytes)?;
        if type_id == TAG_END_ID.0 {
            break Ok(children);
        } else {
            let name = read_string(bytes)?;
            let node = read_node(bytes, nodes, type_id)?;

            match children.binary_search(name.as_ref()) {
                Ok(_) => bail!("read_compound: duplicate key"),
                Err(index) => {
                    children.0.insert(index, (name.into(), node));
                }
            }
        }
    }
}

fn read_byte_array(bytes: &mut &[u8]) -> anyhow::Result<Vec<i8>> {
    let length: i32 = BigEndian::read(bytes)?;
    if length < 0 {
        bail!("read_byte_array: length cannot be negative");
    } else if bytes.len() < length as _ {
        bail!("read_byte_array: not enough bytes to read byte array");
    }

    let (arr_bytes, rest_bytes) = bytes.split_at(length as _);
    *bytes = rest_bytes;

    let vec: Vec<u8> = arr_bytes.into();
    Ok(unsafe { std::mem::transmute(vec) })
}

fn read_string<'a>(bytes: &mut &'a [u8]) -> anyhow::Result<Cow<'a, str>> {
    let length: u16 = BigEndian::read(bytes)?;
    if bytes.len() < length as _ {
        bail!("read_string: not enough bytes to read string");
    }

    let (str_bytes, rest_bytes) = bytes.split_at(length as _);
    *bytes = rest_bytes;

    Ok(cesu8::from_java_cesu8(str_bytes)?)
}

fn read_list(bytes: &mut &[u8], nodes: &mut Vec<NBTNode>) -> anyhow::Result<(u8, Vec<usize>)> {
    let type_id: u8 = Single::read(bytes)?;

    let length: i32 = BigEndian::read(bytes)?;

    if length <= 0 {
        Ok((type_id, Vec::new()))
    } else if type_id == TAG_END_ID.0 {
        bail!("read_list: type cannot be TAG_END for non-zero length list");
    } else {
        let mut children = Vec::with_capacity(length as _);

        for _ in 0..length {
            children.push(read_node(bytes, nodes, type_id)?);
        }

        Ok((type_id, children))
    }
}

fn read_int_array(bytes: &mut &[u8]) -> anyhow::Result<Vec<i32>> {
    let length: i32 = BigEndian::read(bytes)?;
    if length < 0 {
        bail!("read_int_array: length cannot be negative");
    } else if bytes.len() < (length as usize) * 4 {
        bail!("read_int_array: not enough bytes to read int array");
    }

    let mut values = Vec::with_capacity(length as _);
    for _ in 0..length {
        values.push(BigEndian::read(bytes)?);
    }
    Ok(values)
}

fn read_long_array(bytes: &mut &[u8]) -> anyhow::Result<Vec<i64>> {
    let length: i32 = BigEndian::read(bytes)?;
    if length < 0 {
        bail!("read_long_array: length cannot be negative");
    } else if bytes.len() < (length as usize) * 8 {
        bail!("read_long_array: not enough bytes to read long array");
    }

    let mut values = Vec::with_capacity(length as _);
    for _ in 0..length {
        values.push(BigEndian::read(bytes)?);
    }
    Ok(values)
}