use anyhow::{Result, bail};
const SEGMENT_LEN: usize = 96 * 1024;
const MAGIC: &[u8] = b"R0BF";
const FORMAT_VERSION: u32 = 1;
pub fn segments(binary: &[u8]) -> Result<usize> {
Ok(user_elf_len(binary)?.div_ceil(SEGMENT_LEN))
}
fn user_elf_len(binary: &[u8]) -> Result<usize> {
let Some(rest) = binary.strip_prefix(MAGIC) else {
bail!(
"not a RISC Zero program binary: deploy the guest's `.bin` (from `cargo risczero \
build`), not its ELF"
);
};
let (version, rest) = read_u32(rest)?;
if version != FORMAT_VERSION {
bail!("a RISC Zero program binary of format {version}, which LEZ doesn't load");
}
let (header_len, rest) = read_u32(rest)?;
let rest = rest.get(header_len as usize..).ok_or_else(truncated)?;
let (user_len, rest) = read_u32(rest)?;
let user_len = user_len as usize;
if user_len == 0 || rest.len() <= user_len {
return Err(truncated());
}
Ok(user_len)
}
fn read_u32(bytes: &[u8]) -> Result<(u32, &[u8])> {
let (number, rest) = bytes.split_first_chunk::<4>().ok_or_else(truncated)?;
Ok((u32::from_le_bytes(*number), rest))
}
fn truncated() -> anyhow::Error {
anyhow::anyhow!("a truncated RISC Zero program binary")
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
const KERNEL_LEN: usize = 32_392;
pub(crate) fn program_binary(user_len: usize) -> Vec<u8> {
let header = [7u8; 16];
let mut bin = MAGIC.to_vec();
bin.extend(FORMAT_VERSION.to_le_bytes());
bin.extend((header.len() as u32).to_le_bytes());
bin.extend(header);
bin.extend((user_len as u32).to_le_bytes());
bin.extend(vec![1u8; user_len]);
bin.extend(vec![2u8; KERNEL_LEN]);
bin
}
#[test]
fn counts_the_user_elf_only() {
let bin = program_binary(SEGMENT_LEN - 100);
assert!(bin.len() > SEGMENT_LEN);
assert_eq!(segments(&bin).unwrap(), 1);
assert_eq!(segments(&program_binary(SEGMENT_LEN)).unwrap(), 1);
assert_eq!(segments(&program_binary(SEGMENT_LEN + 1)).unwrap(), 2);
assert_eq!(segments(&program_binary(187_240)).unwrap(), 2);
}
#[test]
fn refuses_an_elf() {
let err = segments(b"\x7fELF\x01\x01\x01\0rest").unwrap_err();
assert!(err.to_string().contains("not its ELF"), "{err}");
}
#[test]
fn refuses_truncated_and_unknown_binaries() {
let bin = program_binary(1000);
for cut in [4, 10, 30, 40, bin.len() - KERNEL_LEN] {
assert!(segments(&bin[..cut]).is_err(), "cut at {cut}");
}
let mut other = bin.clone();
other[4] = 2;
assert!(
segments(&other)
.unwrap_err()
.to_string()
.contains("format 2")
);
}
}