#![deny(clippy::arithmetic_side_effects)]
use std::path::Path;
use crate::error::{Error, Result};
pub const HUNK_UNIT: u32 = 999;
pub const HUNK_NAME: u32 = 1000;
pub const HUNK_CODE: u32 = 1001;
pub const HUNK_DATA: u32 = 1002;
pub const HUNK_BSS: u32 = 1003;
pub const HUNK_RELOC32: u32 = 1004;
pub const HUNK_RELOC16: u32 = 1005;
pub const HUNK_RELOC8: u32 = 1006;
pub const HUNK_EXT: u32 = 1007;
pub const HUNK_SYMBOL: u32 = 1008;
pub const HUNK_DEBUG: u32 = 1009;
pub const HUNK_END: u32 = 1010;
pub const HUNK_HEADER: u32 = 1011;
pub const HUNK_OVERLAY: u32 = 1013;
pub const HUNK_BREAK: u32 = 1014;
pub const HUNK_DREL32: u32 = 1015;
pub const HUNK_DREL16: u32 = 1016;
pub const HUNK_DREL8: u32 = 1017;
pub const HUNK_LIB: u32 = 1018;
pub const HUNK_INDEX: u32 = 1019;
pub const HUNK_RELOC32SHORT: u32 = 1020;
pub const HUNK_RELRELOC32: u32 = 1021;
pub const HUNK_ABSRELOC16: u32 = 1022;
pub const HUNKF_CHIP: u32 = 1 << 30;
pub const HUNKF_FAST: u32 = 1 << 31;
pub const HUNKF_MEMTYPE: u32 = HUNKF_CHIP | HUNKF_FAST;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum MemFlags {
#[default]
Any,
Chip,
Fast,
Explicit(u32),
}
impl MemFlags {
#[must_use]
pub fn bits(self) -> u32 {
match self {
Self::Any => 0,
Self::Chip => HUNKF_CHIP,
Self::Fast => HUNKF_FAST,
Self::Explicit(_) => HUNKF_MEMTYPE,
}
}
#[must_use]
pub fn from_bits(size: u32) -> Self {
match size & HUNKF_MEMTYPE {
HUNKF_CHIP => Self::Chip,
HUNKF_FAST => Self::Fast,
HUNKF_MEMTYPE => Self::Explicit(0),
_ => Self::Any,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Kind {
#[default]
Code,
Data,
Bss,
}
impl Kind {
#[must_use]
pub fn block(self) -> u32 {
match self {
Self::Code => HUNK_CODE,
Self::Data => HUNK_DATA,
Self::Bss => HUNK_BSS,
}
}
#[must_use]
pub fn from_block(block: u32) -> Option<Self> {
match block {
HUNK_CODE => Some(Self::Code),
HUNK_DATA => Some(Self::Data),
HUNK_BSS => Some(Self::Bss),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum RelocForm {
#[default]
Long,
Short,
}
fn put(out: &mut Vec<u8>, value: u32) {
out.extend_from_slice(&value.to_be_bytes());
}
fn put_name(out: &mut Vec<u8>, name: &[u8]) -> Result<()> {
let longs = name.len().div_ceil(4);
let count =
u32::try_from(longs).map_err(|_| Error::Limit("a Hunk symbol name is too long".into()))?;
put(out, count);
out.extend_from_slice(name);
let padded = longs.saturating_mul(4);
out.resize(
out.len().saturating_add(padded.saturating_sub(name.len())),
0,
);
Ok(())
}
#[derive(Clone, Debug, Default)]
pub struct Hunk {
pub kind: Kind,
pub alloc: u64,
pub data: Vec<u8>,
pub memory: MemFlags,
pub relocs: Vec<(u32, Vec<u32>)>,
pub symbols: Vec<(Vec<u8>, u32)>,
}
impl Hunk {
#[must_use]
pub fn last_relocated_long(&self) -> usize {
self.relocs
.iter()
.flat_map(|(_, offsets)| offsets.iter())
.map(|&offset| (offset as usize).saturating_div(4).saturating_add(1))
.max()
.unwrap_or(0)
}
pub fn alloc_longs(&self) -> Result<u32> {
u32::try_from(self.alloc.div_ceil(4))
.map_err(|_| Error::Limit("a Hunk hunk is larger than a size longword can name".into()))
}
}
pub fn write(hunks: &[Hunk], form: RelocForm) -> Result<Vec<u8>> {
let count = u32::try_from(hunks.len())
.map_err(|_| Error::Limit("a Hunk load file has too many hunks".into()))?;
let mut out = Vec::new();
put(&mut out, HUNK_HEADER);
put(&mut out, 0);
put(&mut out, count);
put(&mut out, 0);
put(&mut out, count.saturating_sub(1));
for hunk in hunks {
let longs = hunk.alloc_longs()?;
if longs & HUNKF_MEMTYPE != 0 {
return Err(Error::Limit(
"a Hunk hunk is larger than the memory attribute bits allow".into(),
));
}
put(&mut out, longs | hunk.memory.bits());
if let MemFlags::Explicit(mask) = hunk.memory {
put(&mut out, mask);
}
}
for hunk in hunks {
put(&mut out, hunk.kind.block());
match hunk.kind {
Kind::Bss => put(&mut out, hunk.alloc_longs()?),
_ => {
let kept = content_longs(&hunk.data, hunk.last_relocated_long());
let longs = u32::try_from(kept).map_err(|_| {
Error::Limit("a Hunk hunk is larger than a size longword can name".into())
})?;
put(&mut out, longs);
let bytes = kept.saturating_mul(4).min(hunk.data.len());
let data = hunk.data.get(..bytes).unwrap_or(&hunk.data);
out.extend_from_slice(data);
out.resize(
out.len()
.saturating_add(kept.saturating_mul(4).saturating_sub(data.len())),
0,
);
}
}
write_relocs(&mut out, &hunk.relocs, form)?;
if !hunk.symbols.is_empty() {
put(&mut out, HUNK_SYMBOL);
for (name, value) in &hunk.symbols {
put_name(&mut out, name)?;
put(&mut out, *value);
}
put(&mut out, 0);
}
put(&mut out, HUNK_END);
}
Ok(out)
}
fn content_longs(data: &[u8], floor: usize) -> usize {
let mut longs = data.len().div_ceil(4);
while longs > floor {
let start = longs.saturating_sub(1).saturating_mul(4);
let end = start.saturating_add(4).min(data.len());
if !data.get(start..end).is_none_or(is_zero) {
break;
}
longs = longs.saturating_sub(1);
}
longs
}
fn is_zero(bytes: &[u8]) -> bool {
bytes.iter().all(|&b| b == 0)
}
fn write_relocs(out: &mut Vec<u8>, relocs: &[(u32, Vec<u32>)], form: RelocForm) -> Result<()> {
if relocs.iter().all(|(_, offsets)| offsets.is_empty()) {
return Ok(());
}
match form {
RelocForm::Long => {
put(out, HUNK_RELOC32);
for (target, offsets) in relocs {
if offsets.is_empty() {
continue;
}
let count = u32::try_from(offsets.len()).map_err(|_| {
Error::Limit("too many Hunk relocations for one target hunk".into())
})?;
put(out, count);
put(out, *target);
for &offset in offsets {
put(out, offset);
}
}
put(out, 0);
}
RelocForm::Short => {
put(out, HUNK_RELOC32SHORT);
let start = out.len();
for (target, offsets) in relocs {
if offsets.is_empty() {
continue;
}
let count = u16::try_from(offsets.len()).map_err(|_| {
Error::Limit("too many Hunk short relocations for one target hunk".into())
})?;
let target = u16::try_from(*target)
.map_err(|_| Error::Limit("too many hunks for short relocations".into()))?;
out.extend_from_slice(&count.to_be_bytes());
out.extend_from_slice(&target.to_be_bytes());
for &offset in offsets {
let offset = u16::try_from(offset).map_err(|_| {
Error::Limit("a Hunk short relocation offset is over 64 KiB".into())
})?;
out.extend_from_slice(&offset.to_be_bytes());
}
}
out.extend_from_slice(&0u16.to_be_bytes());
if !out.len().wrapping_sub(start).is_multiple_of(4) {
out.extend_from_slice(&0u16.to_be_bytes());
}
}
}
Ok(())
}
#[derive(Clone, Debug, Default)]
pub struct LoadFile {
pub residents: Vec<Vec<u8>>,
pub sizes: Vec<(u64, MemFlags)>,
pub first: u32,
pub last: u32,
pub hunks: Vec<Hunk>,
}
struct Reader<'b> {
data: &'b [u8],
at: usize,
file: &'b Path,
}
impl<'b> Reader<'b> {
fn long(&mut self) -> Result<u32> {
let end = self
.at
.checked_add(4)
.ok_or_else(|| self.truncated("a longword"))?;
let bytes = self
.data
.get(self.at..end)
.ok_or_else(|| self.truncated("a longword"))?;
self.at = end;
let mut word = [0u8; 4];
word.copy_from_slice(bytes);
Ok(u32::from_be_bytes(word))
}
fn word(&mut self) -> Result<u16> {
let end = self
.at
.checked_add(2)
.ok_or_else(|| self.truncated("a word"))?;
let bytes = self
.data
.get(self.at..end)
.ok_or_else(|| self.truncated("a word"))?;
self.at = end;
let mut half = [0u8; 2];
half.copy_from_slice(bytes);
Ok(u16::from_be_bytes(half))
}
fn bytes(&mut self, len: usize) -> Result<&'b [u8]> {
let end = self
.at
.checked_add(len)
.ok_or_else(|| self.truncated("a block's contents"))?;
let bytes = self
.data
.get(self.at..end)
.ok_or_else(|| self.truncated("a block's contents"))?;
self.at = end;
Ok(bytes)
}
fn name(&mut self) -> Result<Option<&'b [u8]>> {
let longs = self.long()?;
if longs == 0 {
return Ok(None);
}
let len = (longs as usize)
.checked_mul(4)
.ok_or_else(|| self.truncated("a name"))?;
let bytes = self.bytes(len)?;
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
Ok(Some(bytes.get(..end).unwrap_or(bytes)))
}
fn done(&self) -> bool {
self.at >= self.data.len()
}
fn truncated(&self, what: &str) -> Error {
self.bad(format!("{what}, which the file ends in the middle of"))
}
fn bad(&self, what: String) -> Error {
Error::Malformed {
file: self.file.to_path_buf(),
member: None,
offset: self.at as u64,
what,
}
}
}
pub fn read(data: &[u8], file: &Path) -> Result<LoadFile> {
let mut r = Reader { data, at: 0, file };
if r.long()? != HUNK_HEADER {
return Err(r.bad("not a Hunk load file: no HUNK_HEADER".into()));
}
let mut file = LoadFile::default();
while let Some(name) = r.name()? {
file.residents.push(name.to_vec());
}
let table = r.long()?;
file.first = r.long()?;
file.last = r.long()?;
for _ in 0..table {
let size = r.long()?;
let memory = match MemFlags::from_bits(size) {
MemFlags::Explicit(_) => MemFlags::Explicit(r.long()?),
other => other,
};
file.sizes
.push((u64::from(size & !HUNKF_MEMTYPE).saturating_mul(4), memory));
}
while !r.done() {
let block = r.long()?;
let Some(kind) = Kind::from_block(block) else {
if block == HUNK_NAME {
let _ = r.name()?;
continue;
}
return Err(r.bad(format!("unexpected Hunk block {block} where a hunk starts")));
};
let longs = r.long()?;
let len = (longs as usize)
.checked_mul(4)
.ok_or_else(|| r.truncated("a hunk"))?;
let index = file.hunks.len();
let (alloc, memory) = file
.sizes
.get(index)
.copied()
.unwrap_or((len as u64, MemFlags::Any));
let mut hunk = Hunk {
kind,
alloc,
data: Vec::new(),
memory,
relocs: Vec::new(),
symbols: Vec::new(),
};
if kind != Kind::Bss {
hunk.data = r.bytes(len)?.to_vec();
}
loop {
let block = r.long()?;
match block {
HUNK_END => break,
HUNK_RELOC32 => loop {
let count = r.long()?;
if count == 0 {
break;
}
let target = r.long()?;
let mut offsets = Vec::with_capacity(count as usize);
for _ in 0..count {
offsets.push(r.long()?);
}
hunk.relocs.push((target, offsets));
},
HUNK_RELOC32SHORT | HUNK_DREL32 => {
let start = r.at;
loop {
let count = r.word()?;
if count == 0 {
break;
}
let target = u32::from(r.word()?);
let mut offsets = Vec::with_capacity(count as usize);
for _ in 0..count {
offsets.push(u32::from(r.word()?));
}
hunk.relocs.push((target, offsets));
}
if !r.at.wrapping_sub(start).is_multiple_of(4) {
let _ = r.word()?;
}
}
HUNK_SYMBOL => {
while let Some(name) = r.name()? {
let name = name.to_vec();
hunk.symbols.push((name, r.long()?));
}
}
HUNK_DEBUG => {
let longs = r.long()?;
let len = (longs as usize)
.checked_mul(4)
.ok_or_else(|| r.truncated("a debug block"))?;
let _ = r.bytes(len)?;
}
other => {
return Err(r.bad(format!("unexpected Hunk block {other} inside a hunk")));
}
}
}
file.hunks.push(hunk);
}
Ok(file)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Vec<Hunk> {
vec![
Hunk {
kind: Kind::Code,
alloc: 12,
data: vec![0x4e, 0x75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
memory: MemFlags::Any,
relocs: vec![(1, vec![4])],
symbols: vec![(b"_start".to_vec(), 0)],
},
Hunk {
kind: Kind::Data,
alloc: 8,
data: vec![1, 2, 3, 4, 0, 0, 0, 0],
memory: MemFlags::Chip,
relocs: Vec::new(),
symbols: Vec::new(),
},
Hunk {
kind: Kind::Data,
alloc: 12,
data: vec![9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0],
memory: MemFlags::Fast,
relocs: Vec::new(),
symbols: Vec::new(),
},
Hunk {
kind: Kind::Bss,
alloc: 16,
data: Vec::new(),
memory: MemFlags::Any,
relocs: Vec::new(),
symbols: vec![(b"buffer".to_vec(), 0)],
},
]
}
#[test]
fn round_trip() {
let bytes = write(&sample(), RelocForm::Long).expect("write");
let file = read(&bytes, Path::new("test.hunk")).expect("read");
assert_eq!(file.sizes.len(), 4);
assert_eq!(file.first, 0);
assert_eq!(file.last, 3);
assert_eq!(file.sizes[0], (12, MemFlags::Any));
assert_eq!(file.sizes[1], (8, MemFlags::Chip));
assert_eq!(file.sizes[2], (12, MemFlags::Fast));
assert_eq!(file.sizes[3], (16, MemFlags::Any));
assert_eq!(file.hunks[0].data, vec![0x4e, 0x75, 0, 0, 0, 0, 0, 0]);
assert_eq!(file.hunks[0].relocs, vec![(1, vec![4])]);
assert_eq!(file.hunks[0].symbols, vec![(b"_start".to_vec(), 0)]);
assert_eq!(file.hunks[1].data, vec![1, 2, 3, 4]);
assert_eq!(file.hunks[2].data, vec![9, 9, 9, 9]);
assert_eq!(file.hunks[3].kind, Kind::Bss);
assert_eq!(file.hunks[3].symbols, vec![(b"buffer".to_vec(), 0)]);
}
#[test]
fn short_relocs_round_trip() {
let bytes = write(&sample(), RelocForm::Short).expect("write");
let file = read(&bytes, Path::new("test.hunk")).expect("read");
assert_eq!(file.hunks[0].relocs, vec![(1, vec![4])]);
}
#[test]
fn truncation_is_an_error_not_a_panic() {
let bytes = write(&sample(), RelocForm::Long).expect("write");
for cut in 0..bytes.len() {
let _ = read(bytes.get(..cut).expect("in range"), Path::new("test.hunk"));
}
}
#[test]
fn garbage_is_an_error_not_a_panic() {
for seed in 0u32..512 {
let mut bytes = write(&sample(), RelocForm::Long).expect("write");
let at = (seed as usize).wrapping_mul(4) % bytes.len();
for (i, byte) in bytes.iter_mut().skip(at).take(4).enumerate() {
*byte ^= (seed >> (i * 8)) as u8 | 0x5a;
}
let _ = read(&bytes, Path::new("test.hunk"));
}
}
}