use crate::assert::AssertTable;
use crate::debug_symbol::DebugSymbolTable;
use crate::export::ExportTable;
use crate::file::FileTable;
use crate::import::ImportTable;
use crate::line_info::LineInfoTable;
use crate::metadata::MetadataTable;
use crate::parse::{parse_bail, parse_err, ParseAt as _, ParseErrorContext as _, ParseResult};
use crate::scope::ScopeTable;
use crate::section::SectionTable;
use crate::span::SpanTable;
use crate::string::StringTable;
#[derive(Debug)]
pub struct Xo65<'data> {
bytes: Xo65Bytes<'data>,
header: Xo65Header<'data>,
metadata_table: MetadataTable<'data>,
file_table: FileTable<'data>,
section_table: SectionTable<'data>,
import_table: ImportTable<'data>,
export_table: ExportTable<'data>,
debug_symbol_table: DebugSymbolTable<'data>,
line_info_table: LineInfoTable<'data>,
string_table: StringTable<'data>,
assert_table: AssertTable<'data>,
scope_table: ScopeTable<'data>,
span_table: SpanTable<'data>,
}
impl<'data> Xo65<'data> {
pub fn as_bytes(&self) -> &'data [u8] {
self.bytes.as_inner()
}
pub fn header(&self) -> &Xo65Header<'data> {
&self.header
}
pub fn metadata_table(&self) -> &MetadataTable<'data> {
&self.metadata_table
}
pub fn file_table(&self) -> &FileTable<'data> {
&self.file_table
}
pub fn section_table(&self) -> &SectionTable<'data> {
&self.section_table
}
pub fn import_table(&self) -> &ImportTable<'data> {
&self.import_table
}
pub fn export_table(&self) -> &ExportTable<'data> {
&self.export_table
}
pub fn debug_symbol_table(&self) -> &DebugSymbolTable<'data> {
&self.debug_symbol_table
}
pub fn line_info_table(&self) -> &LineInfoTable<'data> {
&self.line_info_table
}
pub fn string_table(&self) -> &StringTable<'data> {
&self.string_table
}
pub fn assert_table(&self) -> &AssertTable<'data> {
&self.assert_table
}
pub fn scope_table(&self) -> &ScopeTable<'data> {
&self.scope_table
}
pub fn span_table(&self) -> &SpanTable<'data> {
&self.span_table
}
pub fn parse(bytes: &'data [u8]) -> ParseResult<Self> {
let bytes = Xo65Bytes {
off: 0,
inner: bytes,
};
let header = bytes
.get(..Xo65Header::BYTE_LEN)
.context("xo65 header not found")?;
let header = Xo65Header::parse(header).context("xo65 header parse error")?;
let metadata_table = bytes
.get(header.metadata_table_range())
.context("metadata table not found")?;
let metadata_table =
MetadataTable::parse(metadata_table).context("metadata table parse error")?;
let file_table = bytes
.get(header.file_table_range())
.context("file table not found")?;
let file_table = FileTable::parse(file_table).context("file table parse error")?;
let section_table = bytes
.get(header.section_table_range())
.context("section table not found")?;
let section_table =
SectionTable::parse(section_table).context("section table parse error")?;
let import_table = bytes
.get(header.import_table_range())
.context("import table not found")?;
let import_table = ImportTable::parse(import_table).context("import table parse error")?;
let export_table = bytes
.get(header.export_table_range())
.context("export table not found")?;
let export_table = ExportTable::parse(export_table).context("export table parse error")?;
let debug_symbol_table = bytes
.get(header.debug_symbol_table_range())
.context("debug symbol table not found")?;
let debug_symbol_table = DebugSymbolTable::parse(debug_symbol_table)
.context("debug symbol table parse error")?;
let line_info_table = bytes
.get(header.line_info_table_range())
.context("line info table not found")?;
let line_info_table =
LineInfoTable::parse(line_info_table).context("line info table parse error")?;
let string_table = bytes
.get(header.string_table_range())
.context("string table not found")?;
let string_table = StringTable::parse(string_table).context("string table parse error")?;
let assert_table = bytes
.get(header.assert_table_range())
.context("assert table not found")?;
let assert_table = AssertTable::parse(assert_table).context("assert table parse error")?;
let scope_table = bytes
.get(header.scope_table_range())
.context("scope table not found")?;
let scope_table = ScopeTable::parse(scope_table).context("scope table parse error")?;
let span_table = bytes
.get(header.span_table_range())
.context("span table not found")?;
let span_table = SpanTable::parse(span_table).context("span table parse error")?;
Ok(Self {
bytes,
header,
metadata_table,
file_table,
section_table,
import_table,
export_table,
debug_symbol_table,
line_info_table,
string_table,
assert_table,
scope_table,
span_table,
})
}
}
#[derive(Debug)]
pub struct Xo65Header<'data> {
bytes: Xo65Bytes<'data>,
flags: Xo65Flags,
metadata_table_off: u32,
metadata_table_len: u32,
file_table_off: u32,
file_table_len: u32,
section_table_off: u32,
section_table_len: u32,
import_table_off: u32,
import_table_len: u32,
export_table_off: u32,
export_table_len: u32,
debug_symbol_table_off: u32,
debug_symbol_table_len: u32,
line_info_table_off: u32,
line_info_table_len: u32,
string_table_off: u32,
string_table_len: u32,
assert_table_off: u32,
assert_table_len: u32,
scope_table_off: u32,
scope_table_len: u32,
span_table_off: u32,
span_table_len: u32,
}
impl<'data> Xo65Header<'data> {
const BYTE_LEN: usize = 0x60;
pub fn as_bytes(&self) -> &'data [u8] {
self.bytes.as_inner()
}
pub fn flags(&self) -> Xo65Flags {
self.flags
}
pub fn metadata_table_offset(&self) -> usize {
self.metadata_table_off as usize
}
pub fn metadata_table_len(&self) -> usize {
self.metadata_table_len as usize
}
pub fn file_table_offset(&self) -> usize {
self.file_table_off as usize
}
pub fn file_table_len(&self) -> usize {
self.file_table_len as usize
}
pub fn section_table_offset(&self) -> usize {
self.section_table_off as usize
}
pub fn section_table_len(&self) -> usize {
self.section_table_len as usize
}
pub fn import_table_offset(&self) -> usize {
self.import_table_off as usize
}
pub fn import_table_len(&self) -> usize {
self.import_table_len as usize
}
pub fn export_table_offset(&self) -> usize {
self.export_table_off as usize
}
pub fn export_table_len(&self) -> usize {
self.export_table_len as usize
}
pub fn debug_symbol_table_offset(&self) -> usize {
self.debug_symbol_table_off as usize
}
pub fn debug_symbol_table_len(&self) -> usize {
self.debug_symbol_table_len as usize
}
pub fn line_info_table_offset(&self) -> usize {
self.line_info_table_off as usize
}
pub fn line_info_table_len(&self) -> usize {
self.line_info_table_len as usize
}
pub fn string_table_offset(&self) -> usize {
self.string_table_off as usize
}
pub fn string_table_len(&self) -> usize {
self.string_table_len as usize
}
pub fn assert_table_offset(&self) -> usize {
self.assert_table_off as usize
}
pub fn assert_table_len(&self) -> usize {
self.assert_table_len as usize
}
pub fn scope_table_offset(&self) -> usize {
self.scope_table_off as usize
}
pub fn scope_table_len(&self) -> usize {
self.scope_table_len as usize
}
pub fn span_table_offset(&self) -> usize {
self.span_table_off as usize
}
pub fn span_table_len(&self) -> usize {
self.span_table_len as usize
}
fn metadata_table_range(&self) -> std::ops::Range<usize> {
let off = self.metadata_table_off as usize;
let len = self.metadata_table_len as usize;
off..off + len
}
fn file_table_range(&self) -> std::ops::Range<usize> {
let off = self.file_table_off as usize;
let len = self.file_table_len as usize;
off..off + len
}
fn section_table_range(&self) -> std::ops::Range<usize> {
let off = self.section_table_off as usize;
let len = self.section_table_len as usize;
off..off + len
}
fn import_table_range(&self) -> std::ops::Range<usize> {
let off = self.import_table_off as usize;
let len = self.import_table_len as usize;
off..off + len
}
fn export_table_range(&self) -> std::ops::Range<usize> {
let off = self.export_table_off as usize;
let len = self.export_table_len as usize;
off..off + len
}
fn debug_symbol_table_range(&self) -> std::ops::Range<usize> {
let off = self.debug_symbol_table_off as usize;
let len = self.debug_symbol_table_len as usize;
off..off + len
}
fn line_info_table_range(&self) -> std::ops::Range<usize> {
let off = self.line_info_table_off as usize;
let len = self.line_info_table_len as usize;
off..off + len
}
fn string_table_range(&self) -> std::ops::Range<usize> {
let off = self.string_table_off as usize;
let len = self.string_table_len as usize;
off..off + len
}
fn assert_table_range(&self) -> std::ops::Range<usize> {
let off = self.assert_table_off as usize;
let len = self.assert_table_len as usize;
off..off + len
}
fn scope_table_range(&self) -> std::ops::Range<usize> {
let off = self.scope_table_off as usize;
let len = self.scope_table_len as usize;
off..off + len
}
fn span_table_range(&self) -> std::ops::Range<usize> {
let off = self.span_table_off as usize;
let len = self.span_table_len as usize;
off..off + len
}
fn parse(bytes: Xo65Bytes<'data>) -> ParseResult<Self> {
const MAGIC: [u8; 4] = *b"Uzna";
const VERSION: u16 = 0x0011;
let mut off = 0;
let magic = <[u8; 4]>::parse_at(&bytes, &mut off)?;
if magic != MAGIC {
parse_bail!("xo65 magic not found");
}
let version = u16::parse_at(&bytes, &mut off)?;
if version != VERSION {
parse_bail!("unsupported version: {version:#X}");
}
let flags = u16::parse_at(&bytes, &mut off)?;
let flags = Xo65Flags(flags);
let metadata_table_off = u32::parse_at(&bytes, &mut off)?;
let metadata_table_len = u32::parse_at(&bytes, &mut off)?;
let file_table_off = u32::parse_at(&bytes, &mut off)?;
let file_table_len = u32::parse_at(&bytes, &mut off)?;
let section_table_off = u32::parse_at(&bytes, &mut off)?;
let section_table_len = u32::parse_at(&bytes, &mut off)?;
let import_table_off = u32::parse_at(&bytes, &mut off)?;
let import_table_len = u32::parse_at(&bytes, &mut off)?;
let export_table_off = u32::parse_at(&bytes, &mut off)?;
let export_table_len = u32::parse_at(&bytes, &mut off)?;
let debug_symbol_table_off = u32::parse_at(&bytes, &mut off)?;
let debug_symbol_table_len = u32::parse_at(&bytes, &mut off)?;
let line_info_table_off = u32::parse_at(&bytes, &mut off)?;
let line_info_table_len = u32::parse_at(&bytes, &mut off)?;
let string_table_off = u32::parse_at(&bytes, &mut off)?;
let string_table_len = u32::parse_at(&bytes, &mut off)?;
let assert_table_off = u32::parse_at(&bytes, &mut off)?;
let assert_table_len = u32::parse_at(&bytes, &mut off)?;
let scope_table_off = u32::parse_at(&bytes, &mut off)?;
let scope_table_len = u32::parse_at(&bytes, &mut off)?;
let span_table_off = u32::parse_at(&bytes, &mut off)?;
let span_table_len = u32::parse_at(&bytes, &mut off)?;
Ok(Self {
bytes,
flags,
metadata_table_off,
metadata_table_len,
file_table_off,
file_table_len,
section_table_off,
section_table_len,
import_table_off,
import_table_len,
export_table_off,
export_table_len,
debug_symbol_table_off,
debug_symbol_table_len,
line_info_table_off,
line_info_table_len,
string_table_off,
string_table_len,
assert_table_off,
assert_table_len,
scope_table_off,
scope_table_len,
span_table_off,
span_table_len,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Xo65Flags(u16);
impl Xo65Flags {
pub fn get(self) -> u16 {
self.0
}
pub fn has_debug_info(self) -> bool {
(self.0 & (1 << 0)) != 0
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Xo65Bytes<'data> {
off: usize,
inner: &'data [u8],
}
impl<'data> Xo65Bytes<'data> {
pub(crate) fn offset_start(&self) -> usize {
self.off
}
pub(crate) fn offset_end(&self) -> usize {
self.off + self.len()
}
pub(crate) fn as_inner(&self) -> &'data [u8] {
self.inner
}
pub(crate) fn len(&self) -> usize {
self.inner.len()
}
pub(crate) fn get<I: Xo65BytesIndex<'data>>(&self, idx: I) -> ParseResult<I::Output> {
idx.get(self)
}
pub(crate) fn first_chunk<const LEN: usize>(&self) -> ParseResult<&'data [u8; LEN]> {
self.inner.first_chunk::<LEN>().ok_or_else(|| {
parse_err!(
"index ..{:#X} is out of range {:#X}..{:#X}",
self.offset_start() + LEN,
self.offset_start(),
self.offset_end()
)
})
}
}
pub(crate) trait Xo65BytesIndex<'data> {
type Output;
fn get(self, bytes: &Xo65Bytes<'data>) -> ParseResult<Self::Output>;
}
impl<'data> Xo65BytesIndex<'data> for usize {
type Output = u8;
fn get(self, bytes: &Xo65Bytes<'data>) -> ParseResult<Self::Output> {
bytes.inner.get(self).copied().ok_or_else(|| {
parse_err!(
"index {:#X} is out of range {:#X}..{:#X}",
bytes.offset_start() + self,
bytes.offset_start(),
bytes.offset_end()
)
})
}
}
impl<'data> Xo65BytesIndex<'data> for std::ops::Range<usize> {
type Output = Xo65Bytes<'data>;
fn get(self, bytes: &Xo65Bytes<'data>) -> ParseResult<Self::Output> {
let std::ops::Range { start, end } = self;
let inner = bytes.inner.get(start..end).ok_or_else(|| {
parse_err!(
"index {:#X}..{:#X} is out of range {:#X}..{:#X}",
bytes.offset_start() + start,
bytes.offset_start() + end,
bytes.offset_start(),
bytes.offset_end()
)
})?;
Ok(Xo65Bytes {
off: bytes.offset_start() + start,
inner,
})
}
}
impl<'data> Xo65BytesIndex<'data> for std::ops::RangeFrom<usize> {
type Output = Xo65Bytes<'data>;
fn get(self, bytes: &Xo65Bytes<'data>) -> ParseResult<Self::Output> {
let start = self.start;
let inner = bytes.inner.get(start..).ok_or_else(|| {
parse_err!(
"index {:#X}.. is out of range {:#X}..{:#X}",
bytes.offset_start() + start,
bytes.offset_start(),
bytes.offset_end()
)
})?;
Ok(Xo65Bytes {
off: bytes.offset_start() + start,
inner,
})
}
}
impl<'data> Xo65BytesIndex<'data> for std::ops::RangeTo<usize> {
type Output = Xo65Bytes<'data>;
fn get(self, bytes: &Xo65Bytes<'data>) -> ParseResult<Self::Output> {
let end = self.end;
let inner = bytes.inner.get(..end).ok_or_else(|| {
parse_err!(
"index ..{:#X} is out of range {:#X}..{:#X}",
bytes.offset_start() + end,
bytes.offset_start(),
bytes.offset_end()
)
})?;
Ok(Xo65Bytes {
off: bytes.offset_start(),
inner,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xo65_bytes() {
let bytes = Xo65Bytes {
off: 0,
inner: &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
};
assert_eq!(bytes.offset_start(), 0);
assert_eq!(bytes.get(4).unwrap(), 4);
assert_eq!(
bytes.get(2..5).unwrap(),
Xo65Bytes {
off: 2,
inner: &[2, 3, 4]
}
);
assert_eq!(
bytes.get(7..).unwrap(),
Xo65Bytes {
off: 7,
inner: &[7, 8, 9]
}
);
assert_eq!(
bytes.get(..3).unwrap(),
Xo65Bytes {
off: 0,
inner: &[0, 1, 2]
}
);
assert_eq!(bytes.first_chunk::<3>().unwrap(), &[0, 1, 2]);
let bytes = bytes.get(2..8).unwrap();
assert_eq!(bytes.offset_start(), 2);
assert_eq!(bytes.get(3).unwrap(), 5);
assert_eq!(
bytes.get(2..4).unwrap(),
Xo65Bytes {
off: 4,
inner: &[4, 5]
}
);
assert_eq!(
bytes.get(3..).unwrap(),
Xo65Bytes {
off: 5,
inner: &[5, 6, 7]
}
);
assert_eq!(
bytes.get(..3).unwrap(),
Xo65Bytes {
off: 2,
inner: &[2, 3, 4]
}
);
assert_eq!(bytes.first_chunk::<3>().unwrap(), &[2, 3, 4]);
}
}