use super::segment_role::SegmentRole;
#[cfg(feature = "collections")]
use crate::collections::{FixedVec, Journal, RingBuffer, Slab, SlotMap, SortedVec};
use hopper_runtime::error::ProgramError;
pub type SegmentId = [u8; 4];
#[inline(always)]
pub const fn segment_id(name: &str) -> SegmentId {
let bytes = name.as_bytes();
let mut hash: u32 = 0x811c_9dc5; let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u32;
hash = hash.wrapping_mul(0x0100_0193); i += 1;
}
hash.to_le_bytes()
}
pub const SEGMENT_ENTRY_SIZE: usize = 16;
pub const SEG_FLAG_LOCKED: u16 = 1 << 0; pub const SEG_FLAG_FROZEN: u16 = 1 << 1; pub const SEG_FLAG_DYNAMIC: u16 = 1 << 2;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SegmentEntry {
pub id: [u8; 4],
offset_bytes: [u8; 4],
size_bytes: [u8; 4],
flags_bytes: [u8; 2],
pub version: u8,
pub _reserved: u8,
}
const _: () = assert!(core::mem::size_of::<SegmentEntry>() == SEGMENT_ENTRY_SIZE);
const _: () = assert!(core::mem::align_of::<SegmentEntry>() == 1);
impl SegmentEntry {
#[inline(always)]
pub const fn new(id: SegmentId, offset: u32, size: u32, flags: u16, version: u8) -> Self {
Self {
id,
offset_bytes: offset.to_le_bytes(),
size_bytes: size.to_le_bytes(),
flags_bytes: flags.to_le_bytes(),
version,
_reserved: 0,
}
}
#[inline(always)]
pub fn offset(&self) -> u32 {
u32::from_le_bytes(self.offset_bytes)
}
#[inline(always)]
pub fn size(&self) -> u32 {
u32::from_le_bytes(self.size_bytes)
}
#[inline(always)]
pub fn flags(&self) -> u16 {
u16::from_le_bytes(self.flags_bytes)
}
#[inline(always)]
pub fn is_locked(&self) -> bool {
self.flags() & SEG_FLAG_LOCKED != 0
}
#[inline(always)]
pub fn is_frozen(&self) -> bool {
self.flags() & SEG_FLAG_FROZEN != 0
}
#[inline(always)]
pub fn role(&self) -> SegmentRole {
SegmentRole::from_flags(self.flags())
}
#[inline(always)]
pub fn set_offset(&mut self, offset: u32) {
self.offset_bytes = offset.to_le_bytes();
}
#[inline(always)]
pub fn set_size(&mut self, size: u32) {
self.size_bytes = size.to_le_bytes();
}
#[inline(always)]
pub fn set_flags(&mut self, flags: u16) {
self.flags_bytes = flags.to_le_bytes();
}
}
pub const REGISTRY_HEADER_SIZE: usize = 4;
pub const MAX_REGISTRY_SEGMENTS: usize = 16;
pub struct SegmentRegistry<'a> {
data: &'a [u8],
count: usize,
entries_offset: usize,
}
pub const REGISTRY_OFFSET: usize = crate::account::HEADER_LEN;
impl<'a> SegmentRegistry<'a> {
#[inline]
pub fn from_account(account_data: &'a [u8]) -> Result<Self, ProgramError> {
let start = REGISTRY_OFFSET;
if account_data.len() < start + REGISTRY_HEADER_SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
let count = u16::from_le_bytes([account_data[start], account_data[start + 1]]) as usize;
if count > MAX_REGISTRY_SEGMENTS {
return Err(ProgramError::InvalidAccountData);
}
let entries_offset = start + REGISTRY_HEADER_SIZE;
let needed = entries_offset + count * SEGMENT_ENTRY_SIZE;
if account_data.len() < needed {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(Self {
data: account_data,
count,
entries_offset,
})
}
#[inline(always)]
pub fn segment_count(&self) -> usize {
self.count
}
#[inline(always)]
pub fn data_region_offset(&self) -> usize {
self.entries_offset + self.count * SEGMENT_ENTRY_SIZE
}
#[inline]
pub fn entry(&self, index: usize) -> Result<&SegmentEntry, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = self.entries_offset + index * SEGMENT_ENTRY_SIZE;
Ok(unsafe { &*(self.data.as_ptr().add(offset) as *const SegmentEntry) })
}
#[inline]
pub fn find(&self, id: &SegmentId) -> Result<(usize, &SegmentEntry), ProgramError> {
let mut i = 0;
while i < self.count {
let offset = self.entries_offset + i * SEGMENT_ENTRY_SIZE;
let entry = unsafe { &*(self.data.as_ptr().add(offset) as *const SegmentEntry) };
if entry.id == *id {
return Ok((i, entry));
}
i += 1;
}
Err(ProgramError::InvalidArgument)
}
#[inline]
pub fn segment_data(&self, id: &SegmentId) -> Result<&'a [u8], ProgramError> {
let (_, entry) = self.find(id)?;
let start = entry.offset() as usize;
let end = start + entry.size() as usize;
if end > self.data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(&self.data[start..end])
}
#[inline]
pub fn segment_overlay<T: super::Pod + super::FixedLayout>(
&self,
id: &SegmentId,
) -> Result<&'a T, ProgramError> {
let data = self.segment_data(id)?;
if data.len() < T::SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(unsafe { &*(data.as_ptr() as *const T) })
}
#[inline]
pub fn iter(&self) -> SegmentIter<'a> {
SegmentIter {
data: self.data,
entries_offset: self.entries_offset,
count: self.count,
pos: 0,
}
}
#[inline(always)]
pub fn registry_size(&self) -> usize {
REGISTRY_HEADER_SIZE + self.count * SEGMENT_ENTRY_SIZE
}
#[inline]
pub const fn required_account_size(
header_size: usize,
segment_count: usize,
total_segment_data: usize,
) -> usize {
header_size + REGISTRY_HEADER_SIZE + segment_count * SEGMENT_ENTRY_SIZE + total_segment_data
}
}
pub struct SegmentIter<'a> {
data: &'a [u8],
entries_offset: usize,
count: usize,
pos: usize,
}
impl<'a> Iterator for SegmentIter<'a> {
type Item = (usize, &'a SegmentEntry);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.count {
return None;
}
let idx = self.pos;
let offset = self.entries_offset + idx * SEGMENT_ENTRY_SIZE;
let entry = unsafe { &*(self.data.as_ptr().add(offset) as *const SegmentEntry) };
self.pos += 1;
Some((idx, entry))
}
}
pub struct SegmentRegistryMut<'a> {
data: &'a mut [u8],
count: usize,
entries_offset: usize,
}
impl<'a> SegmentRegistryMut<'a> {
#[inline]
pub fn from_account_mut(account_data: &'a mut [u8]) -> Result<Self, ProgramError> {
let start = REGISTRY_OFFSET;
if account_data.len() < start + REGISTRY_HEADER_SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
let count = u16::from_le_bytes([account_data[start], account_data[start + 1]]) as usize;
if count > MAX_REGISTRY_SEGMENTS {
return Err(ProgramError::InvalidAccountData);
}
let entries_offset = start + REGISTRY_HEADER_SIZE;
let needed = entries_offset + count * SEGMENT_ENTRY_SIZE;
if account_data.len() < needed {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(Self {
data: account_data,
count,
entries_offset,
})
}
#[inline(always)]
pub fn segment_count(&self) -> usize {
self.count
}
#[inline]
pub fn init(data: &mut [u8], specs: &[(SegmentId, u32, u8)]) -> Result<(), ProgramError> {
let start = REGISTRY_OFFSET;
if specs.len() > MAX_REGISTRY_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
let n = specs.len();
let mut i = 0;
while i < n {
let mut j = i + 1;
while j < n {
if specs[i].0 == specs[j].0 {
return Err(ProgramError::InvalidArgument);
}
j += 1;
}
i += 1;
}
let count = specs.len();
let entries_offset = start + REGISTRY_HEADER_SIZE;
let data_region = entries_offset + count * SEGMENT_ENTRY_SIZE;
data[start] = (count & 0xFF) as u8;
data[start + 1] = ((count >> 8) & 0xFF) as u8;
data[start + 2] = 0; data[start + 3] = 0;
let mut current_offset = data_region as u32;
for (i, &(id, size, version)) in specs.iter().enumerate() {
let entry = SegmentEntry::new(id, current_offset, size, 0, version);
let entry_offset = entries_offset + i * SEGMENT_ENTRY_SIZE;
let dst = &mut data[entry_offset..entry_offset + SEGMENT_ENTRY_SIZE];
unsafe {
core::ptr::copy_nonoverlapping(
&entry as *const SegmentEntry as *const u8,
dst.as_mut_ptr(),
SEGMENT_ENTRY_SIZE,
);
}
current_offset += size;
}
Ok(())
}
#[inline]
pub fn entry_mut(&mut self, index: usize) -> Result<&mut SegmentEntry, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = self.entries_offset + index * SEGMENT_ENTRY_SIZE;
Ok(unsafe { &mut *(self.data.as_mut_ptr().add(offset) as *mut SegmentEntry) })
}
#[inline]
pub fn find_mut(&mut self, id: &SegmentId) -> Result<(usize, &mut SegmentEntry), ProgramError> {
let mut i = 0;
while i < self.count {
let offset = self.entries_offset + i * SEGMENT_ENTRY_SIZE;
let entry = unsafe { &mut *(self.data.as_mut_ptr().add(offset) as *mut SegmentEntry) };
if entry.id == *id {
return Ok((i, entry));
}
i += 1;
}
Err(ProgramError::InvalidArgument)
}
#[inline]
pub fn segment_data_mut(&mut self, id: &SegmentId) -> Result<&mut [u8], ProgramError> {
let (_, entry) = self.find_mut(id)?;
if entry.is_locked() || entry.is_frozen() {
return Err(ProgramError::InvalidAccountData);
}
if entry.role().is_immutable_after_init() {
return Err(ProgramError::InvalidAccountData);
}
let start = entry.offset() as usize;
let size = entry.size() as usize;
let end = start + size;
if end > self.data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(&mut self.data[start..end])
}
#[inline]
pub fn segment_data_mut_unchecked(
&mut self,
id: &SegmentId,
) -> Result<&mut [u8], ProgramError> {
let (_, entry) = self.find_mut(id)?;
if entry.is_locked() || entry.is_frozen() {
return Err(ProgramError::InvalidAccountData);
}
let start = entry.offset() as usize;
let size = entry.size() as usize;
let end = start + size;
if end > self.data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(&mut self.data[start..end])
}
#[inline]
pub fn segment_overlay_mut<T: super::Pod + super::FixedLayout>(
&mut self,
id: &SegmentId,
) -> Result<&mut T, ProgramError> {
let data = self.segment_data_mut(id)?;
if data.len() < T::SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(unsafe { &mut *(data.as_mut_ptr() as *mut T) })
}
#[cfg(feature = "collections")]
#[inline]
pub fn segment_fixed_vec<T: super::Pod + super::FixedLayout>(
&mut self,
id: &SegmentId,
) -> Result<FixedVec<'_, T>, ProgramError> {
FixedVec::from_bytes(self.segment_data_mut(id)?)
}
#[cfg(feature = "collections")]
#[inline]
pub fn segment_sorted_vec<T: super::Pod + super::FixedLayout + Ord>(
&mut self,
id: &SegmentId,
) -> Result<SortedVec<'_, T>, ProgramError> {
SortedVec::from_bytes(self.segment_data_mut(id)?)
}
#[cfg(feature = "collections")]
#[inline]
pub fn segment_ring_buffer<T: super::Pod + super::FixedLayout>(
&mut self,
id: &SegmentId,
) -> Result<RingBuffer<'_, T>, ProgramError> {
RingBuffer::from_bytes(self.segment_data_mut(id)?)
}
#[cfg(feature = "collections")]
#[inline]
pub fn segment_slot_map<T: super::Pod + super::FixedLayout>(
&mut self,
id: &SegmentId,
) -> Result<SlotMap<'_, T>, ProgramError> {
SlotMap::from_bytes(self.segment_data_mut(id)?)
}
#[cfg(feature = "collections")]
#[inline]
pub fn segment_journal<T: super::Pod + super::FixedLayout>(
&mut self,
id: &SegmentId,
) -> Result<Journal<'_, T>, ProgramError> {
Journal::from_bytes_mut(self.segment_data_mut(id)?)
}
#[cfg(feature = "collections")]
#[inline]
pub fn segment_slab<T: super::Pod + super::FixedLayout>(
&mut self,
id: &SegmentId,
) -> Result<Slab<'_, T>, ProgramError> {
Slab::from_bytes_mut(self.segment_data_mut(id)?)
}
#[inline]
pub fn freeze_segment(&mut self, id: &SegmentId) -> Result<(), ProgramError> {
let (_, entry) = self.find_mut(id)?;
let new_flags = entry.flags() | SEG_FLAG_FROZEN;
entry.set_flags(new_flags);
Ok(())
}
#[inline]
pub fn unfreeze_segment(&mut self, id: &SegmentId) -> Result<(), ProgramError> {
let (_, entry) = self.find_mut(id)?;
let new_flags = entry.flags() & !SEG_FLAG_FROZEN;
entry.set_flags(new_flags);
Ok(())
}
#[inline]
pub fn lock_segment(&mut self, id: &SegmentId) -> Result<(), ProgramError> {
let (_, entry) = self.find_mut(id)?;
let new_flags = entry.flags() | SEG_FLAG_LOCKED;
entry.set_flags(new_flags);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[repr(C)]
#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
struct Entry8 {
value: u8,
}
#[cfg(feature = "hopper-native-backend")]
unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Zeroable for Entry8 {}
#[cfg(feature = "hopper-native-backend")]
unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Pod for Entry8 {}
unsafe impl crate::account::Pod for Entry8 {}
impl crate::account::FixedLayout for Entry8 {
const SIZE: usize = 1;
}
#[cfg(feature = "collections")]
#[test]
fn segment_fixed_vec_adapter_exposes_vec_api() {
const CORE: SegmentId = segment_id("core");
let total = REGISTRY_OFFSET + REGISTRY_HEADER_SIZE + SEGMENT_ENTRY_SIZE + 8;
let mut account = std::vec![0u8; total];
SegmentRegistryMut::init(&mut account, &[(CORE, 8, 1)]).unwrap();
let mut registry = SegmentRegistryMut::from_account_mut(&mut account).unwrap();
let mut values = registry.segment_fixed_vec::<Entry8>(&CORE).unwrap();
values.push(Entry8 { value: 7 }).unwrap();
values.push(Entry8 { value: 9 }).unwrap();
assert_eq!(values.len(), 2);
assert_eq!(values.get(0).unwrap().value, 7);
assert_eq!(values.get(1).unwrap().value, 9);
}
#[cfg(feature = "collections")]
#[test]
fn segment_journal_adapter_exposes_journal_api() {
const AUDIT: SegmentId = segment_id("audit");
let segment_bytes = crate::collections::JOURNAL_HEADER_SIZE + 4;
let total = REGISTRY_OFFSET + REGISTRY_HEADER_SIZE + SEGMENT_ENTRY_SIZE + segment_bytes;
let mut account = std::vec![0u8; total];
SegmentRegistryMut::init(&mut account, &[(AUDIT, segment_bytes as u32, 1)]).unwrap();
{
let mut registry = SegmentRegistryMut::from_account_mut(&mut account).unwrap();
let mut journal = registry.segment_journal::<Entry8>(&AUDIT).unwrap();
journal.init(false);
journal.append(Entry8 { value: 3 }).unwrap();
journal.append(Entry8 { value: 4 }).unwrap();
}
let registry = SegmentRegistry::from_account(&account).unwrap();
let bytes = registry.segment_data(&AUDIT).unwrap();
let reader = crate::collections::Journal::<Entry8>::from_bytes(bytes).unwrap();
assert_eq!(reader.entry_count(), 2);
assert_eq!(reader.read(0).unwrap().value, 3);
assert_eq!(reader.read(1).unwrap().value, 4);
}
}