use crate::error::ProgramError;
use crate::field_map::{FieldInfo, FieldMap};
use crate::ProgramResult;
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct HopperHeader {
pub disc: u8,
pub version: u8,
pub flags: u16,
pub layout_id: [u8; 8],
pub schema_epoch: u32,
}
impl HopperHeader {
pub const SIZE: usize = 16;
#[inline(always)]
pub fn from_bytes(data: &[u8]) -> Option<&Self> {
if data.len() < Self::SIZE {
return None;
}
Some(unsafe { &*(data.as_ptr() as *const Self) })
}
#[inline(always)]
pub fn from_bytes_mut(data: &mut [u8]) -> Option<&mut Self> {
if data.len() < Self::SIZE {
return None;
}
Some(unsafe { &mut *(data.as_mut_ptr() as *mut Self) })
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LayoutInfo {
pub disc: u8,
pub version: u8,
pub flags: u16,
pub layout_id: [u8; 8],
pub schema_epoch: u32,
pub data_len: usize,
}
impl LayoutInfo {
#[inline(always)]
pub fn from_data(data: &[u8]) -> Option<Self> {
let hdr = HopperHeader::from_bytes(data)?;
let schema_epoch = hdr.schema_epoch;
let layout_id = hdr.layout_id;
Some(Self {
disc: hdr.disc,
version: hdr.version,
flags: hdr.flags,
layout_id,
schema_epoch,
data_len: data.len(),
})
}
#[inline(always)]
pub fn matches<T: LayoutContract>(&self) -> bool {
self.disc == T::DISC
&& self.version == T::VERSION
&& self.layout_id == T::LAYOUT_ID
&& self.data_len >= T::required_len()
}
#[inline(always)]
pub const fn body_len(&self) -> usize {
self.data_len.saturating_sub(HopperHeader::SIZE)
}
#[inline(always)]
pub const fn has_bytes_after(&self, offset: usize) -> bool {
self.data_len > offset
}
}
pub trait LayoutContract: Sized + Copy + FieldMap {
const DISC: u8;
const VERSION: u8;
const LAYOUT_ID: [u8; 8];
const SIZE: usize;
const TYPE_OFFSET: usize = HopperHeader::SIZE;
const RESERVED_BYTES: usize = 0;
const EXTENSION_OFFSET: Option<usize> = None;
#[inline(always)]
fn validate_header(data: &[u8]) -> ProgramResult {
if data.len() < Self::required_len() {
return ProgramError::err_data_too_small();
}
let disc = read_disc(data);
if disc != Some(Self::DISC) {
return ProgramError::err_invalid_data();
}
let version = read_version(data);
if version != Some(Self::VERSION) {
return ProgramError::err_invalid_data();
}
if let Some(id) = read_layout_id(data) {
if *id != Self::LAYOUT_ID {
return ProgramError::err_invalid_data();
}
} else {
return ProgramError::err_data_too_small();
}
Ok(())
}
#[inline(always)]
fn projected_len() -> usize {
Self::TYPE_OFFSET + core::mem::size_of::<Self>()
}
#[inline(always)]
fn required_len() -> usize {
if Self::SIZE > Self::projected_len() {
Self::SIZE
} else {
Self::projected_len()
}
}
#[inline(always)]
fn validate(data: &[u8]) -> bool {
Self::validate_header(data).is_ok()
}
#[inline(always)]
fn check_disc(data: &[u8]) -> ProgramResult {
match read_disc(data) {
Some(d) if d == Self::DISC => Ok(()),
_ => ProgramError::err_invalid_data(),
}
}
#[inline(always)]
fn check_version(data: &[u8]) -> ProgramResult {
match read_version(data) {
Some(v) if v == Self::VERSION => Ok(()),
_ => ProgramError::err_invalid_data(),
}
}
#[inline(always)]
fn compatible(version: u8) -> bool {
version == Self::VERSION
}
#[inline(always)]
fn has_extension_region(data: &[u8]) -> bool {
match Self::EXTENSION_OFFSET {
Some(offset) => data.len() > offset,
None => false,
}
}
#[inline(always)]
fn layout_info_static() -> LayoutInfo {
LayoutInfo {
disc: Self::DISC,
version: Self::VERSION,
flags: 0,
layout_id: Self::LAYOUT_ID,
schema_epoch: DEFAULT_SCHEMA_EPOCH,
data_len: Self::required_len(),
}
}
#[inline(always)]
fn fields() -> &'static [FieldInfo] {
Self::FIELDS
}
}
#[inline(always)]
pub fn read_disc(data: &[u8]) -> Option<u8> {
data.first().copied()
}
#[inline(always)]
pub fn read_version(data: &[u8]) -> Option<u8> {
if data.len() < 2 { None } else { Some(data[1]) }
}
#[inline(always)]
pub fn read_layout_id(data: &[u8]) -> Option<&[u8; 8]> {
if data.len() < 12 {
None
} else {
Some(unsafe { &*(data.as_ptr().add(4) as *const [u8; 8]) })
}
}
#[inline(always)]
pub fn read_flags(data: &[u8]) -> Option<u16> {
if data.len() < 4 {
None
} else {
let bytes = [data[2], data[3]];
Some(u16::from_le_bytes(bytes))
}
}
pub const DEFAULT_SCHEMA_EPOCH: u32 = 1;
#[inline(always)]
pub fn write_header(
data: &mut [u8],
disc: u8,
version: u8,
layout_id: &[u8; 8],
) -> ProgramResult {
write_header_with_epoch(data, disc, version, layout_id, DEFAULT_SCHEMA_EPOCH)
}
#[inline(always)]
pub fn write_header_with_epoch(
data: &mut [u8],
disc: u8,
version: u8,
layout_id: &[u8; 8],
schema_epoch: u32,
) -> ProgramResult {
if data.len() < 16 {
return Err(ProgramError::AccountDataTooSmall);
}
data[0] = disc;
data[1] = version;
data[2] = 0;
data[3] = 0;
data[4..12].copy_from_slice(layout_id);
data[12..16].copy_from_slice(&schema_epoch.to_le_bytes());
Ok(())
}
#[inline(always)]
pub fn read_schema_epoch(data: &[u8]) -> Option<u32> {
if data.len() < 16 {
return None;
}
Some(u32::from_le_bytes([data[12], data[13], data[14], data[15]]))
}
#[inline(always)]
pub fn init_header<T: LayoutContract>(data: &mut [u8]) -> ProgramResult {
write_header(data, T::DISC, T::VERSION, &T::LAYOUT_ID)
}