use crate::instruction::Opcode;
use crate::memory::{Memory, PageAccess};
use crate::vm::Pvm;
use crate::{Gas, PVM_PAGE_SIZE};
pub fn deblob(blob: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u32>)> {
let mut offset = 0;
let (jt_len, n) = decode_natural(blob, offset)?;
offset += n;
if offset >= blob.len() {
return None;
}
let z = blob[offset] as usize;
offset += 1;
let (code_len, n) = decode_natural(blob, offset)?;
offset += n;
let mut jump_table = Vec::with_capacity(jt_len);
for _ in 0..jt_len {
if offset + z > blob.len() {
return None;
}
let mut val: u32 = 0;
for i in 0..z {
val |= (blob[offset + i] as u32) << (i * 8);
}
jump_table.push(val);
offset += z;
}
if offset + code_len > blob.len() {
return None;
}
let code = blob[offset..offset + code_len].to_vec();
offset += code_len;
let bitmask_bytes = (code_len + 7) / 8;
if offset + bitmask_bytes > blob.len() {
return None;
}
let packed_bitmask = &blob[offset..offset + bitmask_bytes];
let mut bitmask = vec![0u8; code_len];
let full_bytes = code_len / 8;
for i in 0..full_bytes {
let b = packed_bitmask[i];
let out = &mut bitmask[i * 8..i * 8 + 8];
out[0] = b & 1;
out[1] = (b >> 1) & 1;
out[2] = (b >> 2) & 1;
out[3] = (b >> 3) & 1;
out[4] = (b >> 4) & 1;
out[5] = (b >> 5) & 1;
out[6] = (b >> 6) & 1;
out[7] = (b >> 7) & 1;
}
for i in full_bytes * 8..code_len {
bitmask[i] = (packed_bitmask[i / 8] >> (i % 8)) & 1;
}
Some((code, bitmask, jump_table))
}
pub fn initialize_program(program_blob: &[u8], arguments: &[u8], gas: Gas) -> Option<Pvm> {
let blob = skip_metadata(program_blob);
if blob.len() < 15 {
return None;
}
let mut offset = 0;
let ro_size = read_le_u24(blob, &mut offset)? as u32;
let rw_size = read_le_u24(blob, &mut offset)? as u32;
let heap_pages = read_le_u16(blob, &mut offset)? as u32;
let stack_size = read_le_u24(blob, &mut offset)? as u32;
if offset + ro_size as usize > blob.len() {
return None;
}
let ro_data = &blob[offset..offset + ro_size as usize];
offset += ro_size as usize;
if offset + rw_size as usize > blob.len() {
return None;
}
let rw_data = &blob[offset..offset + rw_size as usize];
offset += rw_size as usize;
let code_len = read_le_u32(blob, &mut offset)? as usize;
if offset + code_len > blob.len() {
return None;
}
let program_data = &blob[offset..offset + code_len];
let (code, bitmask, jump_table) = deblob(program_data)?;
if !validate_basic_blocks(&code, &bitmask, &jump_table) {
return None;
}
let page_round = |x: u32| -> u32 {
((x + PVM_PAGE_SIZE - 1) / PVM_PAGE_SIZE) * PVM_PAGE_SIZE
};
let s = page_round(stack_size); let arg_start = s; let ro_start = arg_start + page_round(arguments.len() as u32);
let rw_start = ro_start + page_round(ro_size);
let heap_start = rw_start + page_round(rw_size);
let heap_end = heap_start + heap_pages * PVM_PAGE_SIZE;
let mem_size = heap_end;
if (mem_size as u64) > (1u64 << 32) {
return None;
}
let mut memory = Memory::new();
map_region(&mut memory, 0, mem_size, PageAccess::ReadWrite);
copy_data(&mut memory, arg_start, arguments);
copy_data(&mut memory, ro_start, ro_data);
copy_data(&mut memory, rw_start, rw_data);
let mut registers = [0u64; 13];
registers[0] = s as u64; registers[1] = s as u64; registers[7] = arg_start as u64; registers[8] = arguments.len() as u64;
tracing::info!(
"PVM init (linear): stack=[0,{:#x}), args={:#x}+{}, ro={:#x}+{}, rw={:#x}+{}, heap={:#x}..{:#x}, SP={:#x}",
s, arg_start, arguments.len(), ro_start, ro_size, rw_start, rw_size, heap_start, heap_end, registers[0]
);
let mut pvm = Pvm::new(code, bitmask, jump_table, registers, memory, gas);
pvm.heap_base = heap_start;
pvm.heap_top = heap_end;
Some(pvm)
}
pub struct DataLayout {
pub mem_size: u32,
pub arg_start: u32,
pub arg_data: Vec<u8>,
pub ro_start: u32,
pub ro_data: Vec<u8>,
pub rw_start: u32,
pub rw_data: Vec<u8>,
}
pub struct ParsedProgram {
pub code: Vec<u8>,
pub bitmask: Vec<u8>,
pub jump_table: Vec<u32>,
pub registers: [u64; crate::PVM_REGISTER_COUNT],
pub memory: Memory,
pub heap_base: u32,
pub heap_top: u32,
pub layout: Option<DataLayout>,
}
pub fn parse_program_blob(program_blob: &[u8], arguments: &[u8], _gas: Gas, meta_only: bool) -> Option<ParsedProgram> {
let blob = skip_metadata(program_blob);
if blob.len() < 15 {
return None;
}
let mut offset = 0;
let ro_size = read_le_u24(blob, &mut offset)? as u32;
let rw_size = read_le_u24(blob, &mut offset)? as u32;
let heap_pages = read_le_u16(blob, &mut offset)? as u32;
let stack_size = read_le_u24(blob, &mut offset)? as u32;
if offset + ro_size as usize > blob.len() { return None; }
let ro_data = &blob[offset..offset + ro_size as usize];
offset += ro_size as usize;
if offset + rw_size as usize > blob.len() { return None; }
let rw_data = &blob[offset..offset + rw_size as usize];
offset += rw_size as usize;
let code_len = read_le_u32(blob, &mut offset)? as usize;
if offset + code_len > blob.len() { return None; }
let program_data = &blob[offset..offset + code_len];
let (code, bitmask, jump_table) = deblob(program_data)?;
if !validate_basic_blocks(&code, &bitmask, &jump_table) {
return None;
}
let page_round = |x: u32| -> u32 {
((x + PVM_PAGE_SIZE - 1) / PVM_PAGE_SIZE) * PVM_PAGE_SIZE
};
let s = page_round(stack_size);
let arg_start = s;
let ro_start = arg_start + page_round(arguments.len() as u32);
let rw_start = ro_start + page_round(ro_size);
let heap_start = rw_start + page_round(rw_size);
let heap_end = heap_start + heap_pages * PVM_PAGE_SIZE;
let mem_size = heap_end;
if (mem_size as u64) > (1u64 << 32) { return None; }
let mut memory = Memory::new();
let layout = if meta_only {
Some(DataLayout {
mem_size,
arg_start,
arg_data: arguments.to_vec(),
ro_start,
ro_data: ro_data.to_vec(),
rw_start,
rw_data: rw_data.to_vec(),
})
} else {
map_region(&mut memory, 0, mem_size, PageAccess::ReadWrite);
copy_data(&mut memory, arg_start, arguments);
copy_data(&mut memory, ro_start, ro_data);
copy_data(&mut memory, rw_start, rw_data);
None
};
let mut registers = [0u64; crate::PVM_REGISTER_COUNT];
registers[0] = s as u64;
registers[1] = s as u64;
registers[7] = arg_start as u64;
registers[8] = arguments.len() as u64;
Some(ParsedProgram {
code, bitmask, jump_table, registers, memory,
heap_base: heap_start,
heap_top: heap_end,
layout,
})
}
fn validate_basic_blocks(code: &[u8], bitmask: &[u8], jump_table: &[u32]) -> bool {
if code.is_empty() {
return false;
}
let mut last = code.len() - 1;
while last > 0 && (last >= bitmask.len() || bitmask[last] != 1) {
last -= 1;
}
if last >= bitmask.len() || bitmask[last] != 1 {
return false;
}
match Opcode::from_byte(code[last]) {
Some(op) if op.is_terminator() => {}
_ => return false,
}
for &target in jump_table {
let t = target as usize;
if t != 0 && (t >= bitmask.len() || bitmask[t] != 1) {
return false;
}
}
true
}
fn copy_data(memory: &mut Memory, base: u32, data: &[u8]) {
for (i, &byte) in data.iter().enumerate() {
memory.write_u8(base + i as u32, byte);
}
}
fn decode_natural(data: &[u8], offset: usize) -> Option<(usize, usize)> {
if offset >= data.len() {
return None;
}
let first = data[offset];
if first < 128 {
Some((first as usize, 1))
} else if first < 192 {
if offset + 2 > data.len() {
return None;
}
let val = ((first as usize & 0x3F) << 8) | data[offset + 1] as usize;
Some((val, 2))
} else if first < 224 {
if offset + 3 > data.len() {
return None;
}
let val = ((first as usize & 0x1F) << 16)
| ((data[offset + 2] as usize) << 8)
| data[offset + 1] as usize;
Some((val, 3))
} else {
if offset + 4 > data.len() {
return None;
}
let val = ((first as usize & 0x0F) << 24)
| ((data[offset + 3] as usize) << 16)
| ((data[offset + 2] as usize) << 8)
| data[offset + 1] as usize;
Some((val, 4))
}
}
fn map_region(memory: &mut Memory, base: u32, size: u32, access: PageAccess) {
if size == 0 {
return;
}
let start_page = base / PVM_PAGE_SIZE;
let num_pages = (size + PVM_PAGE_SIZE - 1) / PVM_PAGE_SIZE;
for i in 0..num_pages {
memory.map_page(start_page + i, access);
}
}
fn map_region_with_data(memory: &mut Memory, base: u32, data: &[u8], size: u32, access: PageAccess) {
if size == 0 {
return;
}
let start_page = base / PVM_PAGE_SIZE;
let num_pages = (size + PVM_PAGE_SIZE - 1) / PVM_PAGE_SIZE;
let page_size = PVM_PAGE_SIZE as usize;
for i in 0..num_pages {
let data_offset = i as usize * page_size;
if data_offset < data.len() {
let end = (data_offset + page_size).min(data.len());
memory.map_page_with_data(start_page + i, access, &data[data_offset..end]);
} else {
memory.map_page(start_page + i, access);
}
}
}
fn read_le_u16(data: &[u8], offset: &mut usize) -> Option<u16> {
if *offset + 2 > data.len() {
return None;
}
let val = u16::from_le_bytes([data[*offset], data[*offset + 1]]);
*offset += 2;
Some(val)
}
fn skip_metadata(blob: &[u8]) -> &[u8] {
if blob.len() < 14 {
return blob;
}
let ro_size = blob[0] as u32 | ((blob[1] as u32) << 8) | ((blob[2] as u32) << 16);
if (ro_size as usize) + 14 <= blob.len() {
return blob;
}
if let Some((meta_len, consumed)) = decode_natural(blob, 0) {
let skip = consumed + meta_len;
if skip < blob.len() {
return &blob[skip..];
}
}
blob
}
fn read_le_u32(data: &[u8], offset: &mut usize) -> Option<u32> {
if *offset + 4 > data.len() {
return None;
}
let val = u32::from_le_bytes([
data[*offset],
data[*offset + 1],
data[*offset + 2],
data[*offset + 3],
]);
*offset += 4;
Some(val)
}
fn read_le_u24(data: &[u8], offset: &mut usize) -> Option<u32> {
if *offset + 3 > data.len() {
return None;
}
let val = data[*offset] as u32 | ((data[*offset + 1] as u32) << 8) | ((data[*offset + 2] as u32) << 16);
*offset += 3;
Some(val)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deblob_simple() {
let mut blob = Vec::new();
blob.push(0); blob.push(1); blob.push(3); blob.extend_from_slice(&[0, 1, 0]); blob.push(0x07); let (code, bitmask, jt) = deblob(&blob).unwrap();
assert_eq!(code, vec![0, 1, 0]);
assert_eq!(bitmask, vec![1, 1, 1]);
assert!(jt.is_empty());
}
#[test]
fn test_deblob_with_jump_table() {
let mut blob = Vec::new();
blob.push(2); blob.push(2); blob.push(2); blob.extend_from_slice(&[0, 0]); blob.extend_from_slice(&[1, 0]); blob.extend_from_slice(&[0, 1]); blob.push(0x03); let (code, bitmask, jt) = deblob(&blob).unwrap();
assert_eq!(code, vec![0, 1]);
assert_eq!(bitmask, vec![1, 1]);
assert_eq!(jt, vec![0, 1]);
}
#[test]
fn test_invalid_blob() {
assert!(deblob(&[]).is_none());
assert!(deblob(&[0]).is_none()); }
}