hpkg 1.0.0

A native Rust crate to parse Haiku's binary package and repo formats
Documentation
use std::fmt;
use std::io;
use std::io::Read;
use std::slice;

// heap compression
pub const B_HPKG_COMPRESSION_NONE: u16 = 0;
pub const B_HPKG_COMPRESSION_ZLIB: u16 = 1;
pub const B_HPKG_COMPRESSION_ZSTD: u16 = 2;

// attribute type constants (from HPKGDefs.h)
pub const HPKG_ATTR_TYPE_INT: u16 = 1;
pub const HPKG_ATTR_TYPE_UINT: u16 = 2;
pub const HPKG_ATTR_TYPE_STRING: u16 = 3;
pub const HPKG_ATTR_TYPE_RAW: u16 = 4;

// attribute encodings
pub const HPKG_ATTR_ENCODING_INT_8_BIT: u16 = 0;
pub const HPKG_ATTR_ENCODING_INT_16_BIT: u16 = 1;
pub const HPKG_ATTR_ENCODING_INT_32_BIT: u16 = 2;
pub const HPKG_ATTR_ENCODING_INT_64_BIT: u16 = 3;
pub const HPKG_ATTR_ENCODING_STRING_INLINE: u16 = 0;
pub const HPKG_ATTR_ENCODING_STRING_TABLE: u16 = 1;
pub const HPKG_ATTR_ENCODING_RAW_INLINE: u16 = 0;
pub const HPKG_ATTR_ENCODING_RAW_HEAP: u16 = 1;

// Architecture enum values
pub const ARCH_ANY: u64 = 0;
pub const ARCH_X86: u64 = 1;
pub const ARCH_X86_GCC2: u64 = 2;
pub const ARCH_SOURCE: u64 = 3;
pub const ARCH_X86_64: u64 = 4;
pub const ARCH_PPC: u64 = 5;
pub const ARCH_ARM: u64 = 6;
pub const ARCH_M68K: u64 = 7;
pub const ARCH_SPARC: u64 = 8;
pub const ARCH_ARM64: u64 = 9;
pub const ARCH_RISCV64: u64 = 10;

/// Intermediate representation of an attribute value while parsing.
#[derive(Debug, Clone)]
pub enum AttrValue {
	Int(i64),
	Uint(u64),
	String(String),
	Raw(Vec<u8>),
}

/// Read an unsigned LEB128-encoded integer from a byte slice.
pub fn read_unsigned_leb128(data: &[u8], offset: &mut usize) -> Result<u64, Box<dyn std::error::Error>> {
	let mut result: u64 = 0;
	let mut shift = 0;
	loop {
		if *offset >= data.len() {
			return Err(From::from("Unexpected end of data while reading LEB128".to_string()));
		}
		let byte = data[*offset];
		*offset += 1;
		result |= ((byte & 0x7f) as u64) << shift;
		if byte & 0x80 == 0 {
			return Ok(result);
		}
		shift += 7;
		if shift >= 64 {
			return Err(From::from("LEB128 integer too large".to_string()));
		}
	}
}

/// Decode a HPKG attribute tag into its components.
pub fn decode_attribute_tag(tag: u64) -> (u16, u16, u16, bool) {
	let raw = (tag as u16).wrapping_sub(1);
	let id = raw & 0x7f;
	let type_ = (raw >> 7) & 0x7;
	let has_children = (raw >> 10) & 0x1 != 0;
	let encoding = (raw >> 11) & 0x3;
	(id, type_, encoding, has_children)
}

/// Convert an architecture constant to a human-readable string.
pub fn arch_to_string(value: u64) -> String {
	match value {
		ARCH_ANY => "any".to_string(),
		ARCH_X86 => "x86".to_string(),
		ARCH_X86_GCC2 => "x86_gcc2".to_string(),
		ARCH_SOURCE => "source".to_string(),
		ARCH_X86_64 => "x86_64".to_string(),
		ARCH_PPC => "ppc".to_string(),
		ARCH_ARM => "arm".to_string(),
		ARCH_M68K => "m68k".to_string(),
		ARCH_SPARC => "sparc".to_string(),
		ARCH_ARM64 => "arm64".to_string(),
		ARCH_RISCV64 => "riscv64".to_string(),
		_ => format!("arch_{}", value),
	}
}

/// Read a struct from a reader via unsafe zero-initialization.
pub fn read_struct<T, R: Read>(mut read: R) -> io::Result<T> {
	let num_bytes = ::std::mem::size_of::<T>();
	unsafe {
		let mut s = ::std::mem::zeroed();
		let buffer = slice::from_raw_parts_mut(&mut s as *mut T as *mut u8, num_bytes);
		match read.read_exact(buffer) {
			Ok(()) => Ok(s),
			Err(e) => Err(e),
		}
	}
}

impl fmt::Display for AttrValue {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			AttrValue::Int(v) => write!(f, "int({})", v),
			AttrValue::Uint(v) => write!(f, "uint({})", v),
			AttrValue::String(s) => write!(f, "\"{}\"", s),
			AttrValue::Raw(r) => write!(f, "raw({} bytes)", r.len()),
		}
	}
}

// ---------------------------------------------------------------------------
// Shared serialization helpers (used by package_writer and repository_writer)
// ---------------------------------------------------------------------------

/// Write an unsigned LEB128-encoded integer.
pub(crate) fn write_unsigned_leb128(buf: &mut Vec<u8>, mut value: u64) {
	loop {
		let mut byte = (value as u8) & 0x7f;
		value >>= 7;
		if value != 0 {
			byte |= 0x80;
		}
		buf.push(byte);
		if value == 0 {
			break;
		}
	}
}

/// Build and write an attribute tag (LEB128).
pub(crate) fn write_attr_tag(buf: &mut Vec<u8>, id: u16, type_: u16, encoding: u16, has_children: bool) {
	let tag: u64 = 1
		+ ((encoding as u64) << 11)
		+ ((has_children as u64) << 10)
		+ ((type_ as u64) << 7)
		+ (id as u64);
	write_unsigned_leb128(buf, tag);
}

/// Write a null-terminated inline string.
pub(crate) fn write_string_inline(buf: &mut Vec<u8>, s: &str) {
	buf.extend_from_slice(s.as_bytes());
	buf.push(0);
}

/// Choose the smallest encoding for an unsigned integer.
pub(crate) fn uint_encoding(value: u64) -> u16 {
	if value <= u8::MAX as u64 {
		0
	} else if value <= u16::MAX as u64 {
		1
	} else if value <= u32::MAX as u64 {
		2
	} else {
		3
	}
}

/// Write an unsigned integer value to the buffer using the specified encoding.
pub(crate) fn write_int_value(buf: &mut Vec<u8>, value: u64, encoding: u16) {
	match encoding {
		0 => buf.push(value as u8),
		1 => buf.extend_from_slice(&(value as u16).to_be_bytes()),
		2 => buf.extend_from_slice(&(value as u32).to_be_bytes()),
		3 => buf.extend_from_slice(&value.to_be_bytes()),
		_ => buf.extend_from_slice(&(value as u32).to_be_bytes()),
	}
}