use alloc::string::String;
use alloc::vec::Vec;
pub const SPAWN_REF_MAGIC: [u8; 8] = [0x00, b'm', b'y', b'r', b'm', b'S', b'P', b'N'];
pub const SPAWN_REF_MAX_NAME: usize = 128;
const HASH_AT: usize = 8;
const NAME_LEN_AT: usize = HASH_AT + 32;
const NAME_AT: usize = NAME_LEN_AT + 4;
#[repr(C)]
pub struct SpawnRef<const N: usize> {
magic: [u8; 8],
hash: [u8; 32],
name_len: u32,
name: [u8; N],
}
impl<const N: usize> SpawnRef<N> {
pub const fn new(name: &str) -> Self {
let src = name.as_bytes();
let mut buf = [0u8; N];
let mut i = 0;
while i < N {
buf[i] = src[i];
i += 1;
}
Self {
magic: SPAWN_REF_MAGIC,
hash: [0xAA; 32],
name_len: N as u32,
name: buf,
}
}
pub const fn hash_ref(&self) -> &[u8; 32] {
&self.hash
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct FoundRef {
pub name: String,
pub hash_offset: usize,
}
pub fn scan_spawn_refs(wasm: &[u8]) -> Vec<FoundRef> {
let mut out = Vec::new();
let mut search = 0;
while let Some(rel) = find(&wasm[search..], &SPAWN_REF_MAGIC) {
let pos = search + rel;
search = pos + 1;
let name_at = pos + NAME_AT;
if name_at > wasm.len() {
break;
}
let name_len = u32::from_le_bytes([
wasm[pos + NAME_LEN_AT],
wasm[pos + NAME_LEN_AT + 1],
wasm[pos + NAME_LEN_AT + 2],
wasm[pos + NAME_LEN_AT + 3],
]) as usize;
if name_len == 0 || name_len > SPAWN_REF_MAX_NAME || name_at + name_len > wasm.len() {
continue;
}
let Ok(name) = core::str::from_utf8(&wasm[name_at..name_at + name_len]) else {
continue;
};
out.push(FoundRef {
name: String::from(name),
hash_offset: pos + HASH_AT,
});
search = name_at + name_len;
}
out
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}
#[cfg(test)]
mod tests {
use super::*;
fn as_bytes<const N: usize>(r: &SpawnRef<N>) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
(r as *const SpawnRef<N>).cast::<u8>(),
core::mem::size_of::<SpawnRef<N>>(),
)
}
}
#[test]
fn scans_name_and_hash_offset_from_real_layout() {
let r = SpawnRef::<5>::new("child");
let mut buf = alloc::vec![0xEEu8; 7]; buf.extend_from_slice(as_bytes(&r));
let found = scan_spawn_refs(&buf);
assert_eq!(found.len(), 1);
assert_eq!(found[0].name, "child");
assert_eq!(
&buf[found[0].hash_offset..found[0].hash_offset + 32],
&[0xAA; 32]
);
}
#[test]
fn scans_multiple_refs() {
let a = SpawnRef::<5>::new("child");
let b = SpawnRef::<6>::new("worker");
let mut buf = alloc::vec![];
buf.extend_from_slice(as_bytes(&a));
buf.extend_from_slice(&[0x11, 0x22, 0x33]); buf.extend_from_slice(as_bytes(&b));
let names: alloc::vec::Vec<_> = scan_spawn_refs(&buf).into_iter().map(|f| f.name).collect();
assert_eq!(names, alloc::vec!["child", "worker"]);
}
#[test]
fn ignores_stray_magic_with_implausible_length() {
let mut buf = alloc::vec![];
buf.extend_from_slice(&SPAWN_REF_MAGIC);
buf.extend_from_slice(&[0xAA; 32]);
buf.extend_from_slice(&u32::MAX.to_le_bytes()); buf.extend_from_slice(b"noise");
assert_eq!(scan_spawn_refs(&buf), alloc::vec![]);
}
}