use crate::{attribute::Attribute, error::Error, header::INVALID_VAL, reader::Reader};
const FLAG_EXTERNAL: u8 = 0x01;
const FLAG_EXPORTED: u8 = 0x02;
const FLAG_HAS_ATTRIBUTES: u8 = 0x04;
const MAX_NAME_LEN: u32 = 0x4000_0000;
#[derive(Clone, Debug)]
pub struct Proc<'a> {
pub flags_raw: u8,
pub kind: ProcKind<'a>,
pub attributes: Vec<Attribute<'a>>,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ProcKind<'a> {
External(ExternalProc<'a>),
Internal(InternalProc<'a>),
}
#[derive(Clone, Debug)]
pub struct ExternalProc<'a> {
pub name: &'a [u8],
pub decl: Option<&'a [u8]>,
}
#[derive(Clone, Debug)]
pub struct InternalProc<'a> {
pub bytecode_offset: u32,
pub bytecode_len: u32,
pub export_name: Option<&'a [u8]>,
pub export_decl: Option<&'a [u8]>,
}
pub(crate) fn parse_proc<'a>(reader: &mut Reader<'a>, blob_len: usize) -> Result<Proc<'a>, Error> {
let flags = reader.u8("proc Flags")?;
let kind = if (flags & FLAG_EXTERNAL) != 0 {
let name_len = reader.u8("external proc name length")?;
let name = reader.take(name_len as usize, "external proc name")?;
let decl = if (flags & FLAG_EXPORTED) != 0 {
let len = reader.u32_le("external proc decl length")?;
if len > MAX_NAME_LEN {
return Err(Error::Overflow {
what: "external proc decl length",
});
}
Some(reader.take(len as usize, "external proc decl bytes")?)
} else {
None
};
ProcKind::External(ExternalProc { name, decl })
} else {
let bytecode_offset = reader.u32_le("internal proc bytecode offset")?;
let bytecode_len = reader.u32_le("internal proc bytecode length")?;
check_bytecode_range(bytecode_offset, bytecode_len, blob_len)?;
let (export_name, export_decl) = if (flags & FLAG_EXPORTED) != 0 {
let name_len = reader.u32_le("internal proc export-name length")?;
if name_len > MAX_NAME_LEN {
return Err(Error::Overflow {
what: "internal proc export-name length",
});
}
let name = reader.take(name_len as usize, "internal proc export-name")?;
let decl_len = reader.u32_le("internal proc export-decl length")?;
if decl_len > MAX_NAME_LEN {
return Err(Error::Overflow {
what: "internal proc export-decl length",
});
}
let decl = reader.take(decl_len as usize, "internal proc export-decl")?;
(Some(name), Some(decl))
} else {
(None, None)
};
ProcKind::Internal(InternalProc {
bytecode_offset,
bytecode_len,
export_name,
export_decl,
})
};
Ok(Proc {
flags_raw: flags,
kind,
attributes: Vec::new(),
})
}
pub(crate) fn has_attributes(flags_raw: u8) -> bool {
(flags_raw & FLAG_HAS_ATTRIBUTES) != 0
}
fn check_bytecode_range(offset: u32, length: u32, blob_len: usize) -> Result<(), Error> {
if length == 0 || offset == INVALID_VAL {
return Err(Error::BytecodeOutOfRange { offset, length });
}
let end = u64::from(offset)
.checked_add(u64::from(length))
.ok_or(Error::Overflow {
what: "internal proc bytecode end offset",
})?;
if end > blob_len as u64 {
return Err(Error::BytecodeOutOfRange { offset, length });
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn put_le32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
#[test]
fn parses_external_proc_without_decl() {
let mut buf = vec![0x01, 6];
buf.extend_from_slice(b"MsgBox");
let mut r = Reader::new(&buf);
let p = parse_proc(&mut r, buf.len()).unwrap();
match p.kind {
ProcKind::External(ext) => {
assert_eq!(ext.name, b"MsgBox");
assert!(ext.decl.is_none());
}
other => panic!("expected External, got {other:?}"),
}
}
#[test]
fn parses_external_proc_with_decl() {
let mut buf = vec![0x03, 3];
buf.extend_from_slice(b"Foo");
put_le32(&mut buf, 11);
buf.extend_from_slice(b"(): Integer");
let mut r = Reader::new(&buf);
let p = parse_proc(&mut r, buf.len()).unwrap();
match p.kind {
ProcKind::External(ext) => {
assert_eq!(ext.name, b"Foo");
assert_eq!(ext.decl, Some(&b"(): Integer"[..]));
}
other => panic!("expected External, got {other:?}"),
}
}
#[test]
fn parses_internal_proc_minimal() {
let mut buf = vec![0x00];
put_le32(&mut buf, 0x10);
put_le32(&mut buf, 0x20);
buf.resize(0x40, 0);
let mut r = Reader::new(&buf);
let p = parse_proc(&mut r, buf.len()).unwrap();
match p.kind {
ProcKind::Internal(int) => {
assert_eq!(int.bytecode_offset, 0x10);
assert_eq!(int.bytecode_len, 0x20);
assert!(int.export_name.is_none());
}
other => panic!("expected Internal, got {other:?}"),
}
}
#[test]
fn rejects_internal_proc_past_blob() {
let mut buf = vec![0x00];
put_le32(&mut buf, 0x10);
put_le32(&mut buf, 0x100);
let mut r = Reader::new(&buf);
let err = parse_proc(&mut r, 0x40).unwrap_err();
assert!(matches!(err, Error::BytecodeOutOfRange { .. }));
}
}