use core::mem::MaybeUninit;
use crate::account_view::AccountView;
use crate::address::Address;
use crate::raw_account::RuntimeAccount;
use crate::MAX_PERMITTED_DATA_INCREASE;
const BPF_ALIGN_OF_U128: usize = 8;
#[inline(never)]
#[cold]
pub(crate) fn malformed_duplicate_marker(marker: u8, slot: usize) -> ! {
#[cfg(target_os = "solana")]
unsafe {
const MSG: &[u8] = b"hopper: malformed duplicate marker";
crate::syscalls::sol_panic_(MSG.as_ptr(), MSG.len() as u64, slot as u64, marker as u64);
}
#[cfg(not(target_os = "solana"))]
{
panic!(
"hopper: malformed duplicate marker at slot {}: marker {} points forward",
slot, marker
);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RawAccountIndex {
pub original_index: usize,
pub duplicate_of: Option<usize>,
}
impl RawAccountIndex {
#[inline(always)]
pub const fn is_duplicate(&self) -> bool {
self.duplicate_of.is_some()
}
}
#[derive(Clone)]
pub struct RawInstructionFrame {
pub accounts_start: *mut u8,
pub account_count: usize,
pub instruction_data: &'static [u8],
pub program_id: Address,
}
#[inline(always)]
pub unsafe fn deserialize_accounts<const MAX: usize>(
input: *mut u8,
accounts: &mut [MaybeUninit<AccountView>; MAX],
) -> (Address, usize, &'static [u8]) {
let frame = unsafe { scan_instruction_frame(input) };
let mut offset = 8usize;
let count = frame.account_count.min(MAX);
let mut slot = 0usize;
while slot < count {
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let raw = unsafe { input.add(offset) as *mut RuntimeAccount };
accounts[slot] = MaybeUninit::new(unsafe { AccountView::new_unchecked(raw) });
let data_len = unsafe { (*raw).data_len as usize };
offset += RuntimeAccount::SIZE;
offset += data_len + MAX_PERMITTED_DATA_INCREASE;
offset += unsafe { input.add(offset).align_offset(BPF_ALIGN_OF_U128) };
offset += 8;
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
let raw = unsafe { accounts[duplicate_of].assume_init_ref().raw_ptr() };
accounts[slot] = MaybeUninit::new(unsafe { AccountView::new_unchecked(raw) });
offset += 8;
}
slot += 1;
}
while slot < frame.account_count {
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let raw = unsafe { input.add(offset) as *const RuntimeAccount };
let data_len = unsafe { (*raw).data_len as usize };
offset += RuntimeAccount::SIZE;
offset += data_len + MAX_PERMITTED_DATA_INCREASE;
offset += unsafe { input.add(offset).align_offset(BPF_ALIGN_OF_U128) };
offset += 8;
} else {
offset += 8;
}
slot += 1;
}
(frame.program_id, count, frame.instruction_data)
}
#[inline(always)]
pub unsafe fn deserialize_accounts_fast<const MAX: usize>(
input: *mut u8,
accounts: &mut [MaybeUninit<AccountView>; MAX],
instruction_data: &'static [u8],
program_id: Address,
) -> (Address, usize, &'static [u8]) {
let num_accounts = unsafe { *(input as *const u64) as usize };
let count = num_accounts.min(MAX);
let mut offset = 8usize;
let mut slot = 0usize;
while slot < count {
let marker = unsafe { *input.add(offset) };
if marker == u8::MAX {
let raw = unsafe { input.add(offset) as *mut RuntimeAccount };
accounts[slot] = MaybeUninit::new(unsafe { AccountView::new_unchecked(raw) });
let data_len = unsafe { (*raw).data_len as usize };
offset += RuntimeAccount::SIZE;
offset += data_len + MAX_PERMITTED_DATA_INCREASE;
offset += unsafe { input.add(offset).align_offset(BPF_ALIGN_OF_U128) };
offset += 8;
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
malformed_duplicate_marker(marker, slot);
}
let raw = unsafe { accounts[duplicate_of].assume_init_ref().raw_ptr() };
accounts[slot] = MaybeUninit::new(unsafe { AccountView::new_unchecked(raw) });
offset += 8;
}
slot += 1;
}
(program_id, count, instruction_data)
}
#[inline(always)]
pub unsafe fn scan_instruction_frame(input: *mut u8) -> RawInstructionFrame {
let mut scan = input;
let num_accounts = unsafe { *(scan as *const u64) as usize };
scan = unsafe { scan.add(8) };
let accounts_start = scan;
let mut slot = 0usize;
while slot < num_accounts {
let marker = unsafe { *scan };
if marker == u8::MAX {
let raw = scan as *const RuntimeAccount;
let data_len = unsafe { (*raw).data_len as usize };
let mut step = RuntimeAccount::SIZE + data_len + MAX_PERMITTED_DATA_INCREASE;
step += unsafe { scan.add(step).align_offset(BPF_ALIGN_OF_U128) };
step += 8;
scan = unsafe { scan.add(step) };
} else {
scan = unsafe { scan.add(8) };
}
slot += 1;
}
let data_len = unsafe { *(scan as *const u64) as usize };
scan = unsafe { scan.add(8) };
let instruction_data = unsafe { core::slice::from_raw_parts(scan as *const u8, data_len) };
scan = unsafe { scan.add(data_len) };
let program_id_ptr = scan as *const [u8; 32];
let program_id = Address::new_from_array(unsafe { *program_id_ptr });
RawInstructionFrame {
accounts_start,
account_count: num_accounts.min(254),
instruction_data,
program_id,
}
}
pub const MAX_SAFE_ACCOUNT_SLOTS: usize = 256;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FrameInfo {
pub account_count: usize,
pub instruction_data_range: core::ops::Range<usize>,
pub program_id_offset: usize,
pub slot_offsets: [usize; MAX_SAFE_ACCOUNT_SLOTS],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameError {
UnexpectedEof { needed: usize, at: usize },
AccountCountOutOfRange(u64),
MalformedDuplicateMarker { slot: usize, marker: u8 },
DataLenOutOfRange { slot: usize, data_len: u64 },
OffsetOverflow { slot: usize },
}
impl core::fmt::Display for FrameError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnexpectedEof { needed, at } => {
write!(f, "unexpected EOF: need {needed} bytes at offset {at}")
}
Self::AccountCountOutOfRange(n) => {
write!(f, "account count {n} exceeds cap 256")
}
Self::MalformedDuplicateMarker { slot, marker } => {
write!(
f,
"malformed duplicate marker at slot {slot}: marker {marker} does not refer to an earlier slot"
)
}
Self::DataLenOutOfRange { slot, data_len } => {
write!(
f,
"slot {slot}: data_len {data_len} exceeds remaining buffer"
)
}
Self::OffsetOverflow { slot } => {
write!(f, "slot {slot}: offset arithmetic overflow")
}
}
}
}
pub fn parse_instruction_frame_checked(buf: &[u8]) -> Result<FrameInfo, FrameError> {
fn read_u64_le(buf: &[u8], pos: &mut usize) -> Result<u64, FrameError> {
let end = pos
.checked_add(8)
.ok_or(FrameError::OffsetOverflow { slot: 0 })?;
let slice = buf.get(*pos..end).ok_or(FrameError::UnexpectedEof {
needed: 8,
at: *pos,
})?;
let mut bytes = [0u8; 8];
bytes.copy_from_slice(slice);
*pos = end;
Ok(u64::from_le_bytes(bytes))
}
fn read_u8(buf: &[u8], pos: &mut usize) -> Result<u8, FrameError> {
let byte = *buf.get(*pos).ok_or(FrameError::UnexpectedEof {
needed: 1,
at: *pos,
})?;
*pos += 1;
Ok(byte)
}
fn advance(buf: &[u8], pos: &mut usize, n: usize) -> Result<(), FrameError> {
let end = pos
.checked_add(n)
.ok_or(FrameError::OffsetOverflow { slot: 0 })?;
if end > buf.len() {
return Err(FrameError::UnexpectedEof {
needed: n,
at: *pos,
});
}
*pos = end;
Ok(())
}
let mut pos = 0usize;
let account_count = read_u64_le(buf, &mut pos)?;
if account_count > MAX_SAFE_ACCOUNT_SLOTS as u64 {
return Err(FrameError::AccountCountOutOfRange(account_count));
}
let account_count = account_count as usize;
let mut slot_offsets = [0usize; MAX_SAFE_ACCOUNT_SLOTS];
for slot in 0..account_count {
let slot_start = pos;
slot_offsets[slot] = slot_start;
let marker = read_u8(buf, &mut pos)?;
if marker == u8::MAX {
advance(buf, &mut pos, RuntimeAccount::SIZE - 1).map_err(|_| {
FrameError::UnexpectedEof {
needed: RuntimeAccount::SIZE - 1,
at: pos,
}
})?;
let data_len_pos = slot_start
.checked_add(80)
.ok_or(FrameError::OffsetOverflow { slot })?;
let mut dl_bytes = [0u8; 8];
let dl_slice =
buf.get(data_len_pos..data_len_pos + 8)
.ok_or(FrameError::UnexpectedEof {
needed: 8,
at: data_len_pos,
})?;
dl_bytes.copy_from_slice(dl_slice);
let data_len = u64::from_le_bytes(dl_bytes);
let data_sz: usize = (data_len as usize)
.checked_add(MAX_PERMITTED_DATA_INCREASE)
.ok_or(FrameError::DataLenOutOfRange { slot, data_len })?;
advance(buf, &mut pos, data_sz)
.map_err(|_| FrameError::DataLenOutOfRange { slot, data_len })?;
let pad = pos.wrapping_neg() & (BPF_ALIGN_OF_U128 - 1);
advance(buf, &mut pos, pad).map_err(|_| FrameError::UnexpectedEof {
needed: pad,
at: pos,
})?;
advance(buf, &mut pos, 8)
.map_err(|_| FrameError::UnexpectedEof { needed: 8, at: pos })?;
} else {
let duplicate_of = marker as usize;
if duplicate_of >= slot {
return Err(FrameError::MalformedDuplicateMarker { slot, marker });
}
advance(buf, &mut pos, 7)
.map_err(|_| FrameError::UnexpectedEof { needed: 7, at: pos })?;
}
}
let ix_data_len = read_u64_le(buf, &mut pos)? as usize;
let ix_start = pos;
advance(buf, &mut pos, ix_data_len).map_err(|_| FrameError::UnexpectedEof {
needed: ix_data_len,
at: pos,
})?;
let instruction_data_range = ix_start..pos;
let program_id_offset = pos;
advance(buf, &mut pos, 32).map_err(|_| FrameError::UnexpectedEof {
needed: 32,
at: pos,
})?;
Ok(FrameInfo {
account_count,
instruction_data_range,
program_id_offset,
slot_offsets,
})
}
#[cfg(test)]
mod checked_parser_tests {
use super::*;
const MINIMAL_FRAME_LEN: usize = 8 + 88 + MAX_PERMITTED_DATA_INCREASE + 0 + 8 + 8 + 32;
fn build_minimal_frame() -> [u8; MINIMAL_FRAME_LEN] {
let mut buf = [0u8; MINIMAL_FRAME_LEN];
buf[0..8].copy_from_slice(&1u64.to_le_bytes()); buf[8] = 0xFF; buf
}
#[test]
fn parses_minimal_valid_frame() {
let buf = build_minimal_frame();
let frame = parse_instruction_frame_checked(&buf).expect("well-formed");
assert_eq!(frame.account_count, 1);
assert_eq!(frame.instruction_data_range.len(), 0);
assert_eq!(frame.program_id_offset + 32, buf.len());
}
#[test]
fn truncated_header_is_rejected() {
let buf = [0u8; 4]; let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(err, FrameError::UnexpectedEof { .. }));
}
#[test]
fn oversized_account_count_is_rejected() {
let mut buf = [0u8; 8];
buf.copy_from_slice(&1_000u64.to_le_bytes());
let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(err, FrameError::AccountCountOutOfRange(1000)));
}
#[test]
fn forward_duplicate_marker_is_rejected() {
let mut buf = [0u8; 16];
buf[0..8].copy_from_slice(&2u64.to_le_bytes());
buf[8] = 1; let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(
err,
FrameError::MalformedDuplicateMarker { slot: 0, marker: 1 }
));
}
#[test]
fn self_duplicate_marker_is_rejected() {
let mut buf = [0u8; 16];
buf[0..8].copy_from_slice(&1u64.to_le_bytes());
buf[8] = 0; let err = parse_instruction_frame_checked(&buf).unwrap_err();
assert!(matches!(
err,
FrameError::MalformedDuplicateMarker { slot: 0, marker: 0 }
));
}
#[test]
fn arbitrary_short_input_never_panics() {
let buf = [0u8; 256];
for len in 0..=256 {
let _ = parse_instruction_frame_checked(&buf[..len]);
}
}
#[test]
fn arbitrary_ff_input_never_panics() {
let buf = [0xFFu8; 256];
for len in 0..=256 {
let _ = parse_instruction_frame_checked(&buf[..len]);
}
}
}