use hopper_runtime::ProgramError;
use super::pod::{FixedLayout, Pod};
pub const SEGMENT_DESC_SIZE: usize = 12;
pub const MAX_SEGMENTS: usize = 8;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SegmentDescriptor {
pub offset: [u8; 4],
pub count: [u8; 2],
pub capacity: [u8; 2],
pub element_size: [u8; 2],
pub flags: [u8; 2],
}
unsafe impl Pod for SegmentDescriptor {}
impl FixedLayout for SegmentDescriptor {
const SIZE: usize = SEGMENT_DESC_SIZE;
}
const _: () = assert!(core::mem::size_of::<SegmentDescriptor>() == SEGMENT_DESC_SIZE);
const _: () = assert!(core::mem::align_of::<SegmentDescriptor>() == 1);
impl SegmentDescriptor {
#[inline(always)]
pub const fn new(offset: u32, count: u16, capacity: u16, element_size: u16) -> Self {
Self {
offset: offset.to_le_bytes(),
count: count.to_le_bytes(),
capacity: capacity.to_le_bytes(),
element_size: element_size.to_le_bytes(),
flags: [0, 0],
}
}
#[inline(always)]
pub const fn offset(&self) -> u32 {
u32::from_le_bytes(self.offset)
}
#[inline(always)]
pub const fn count(&self) -> u16 {
u16::from_le_bytes(self.count)
}
#[inline(always)]
pub const fn capacity(&self) -> u16 {
u16::from_le_bytes(self.capacity)
}
#[inline(always)]
pub const fn element_size(&self) -> u16 {
u16::from_le_bytes(self.element_size)
}
#[inline(always)]
pub const fn flags(&self) -> u16 {
u16::from_le_bytes(self.flags)
}
#[inline(always)]
pub const fn live_data_len(&self) -> usize {
self.count() as usize * self.element_size() as usize
}
#[inline(always)]
pub const fn data_len(&self) -> usize {
self.live_data_len()
}
#[inline(always)]
pub const fn max_data_len(&self) -> usize {
self.capacity() as usize * self.element_size() as usize
}
#[inline(always)]
pub const fn is_full(&self) -> bool {
self.count() >= self.capacity()
}
#[inline(always)]
pub const fn byte_range(&self) -> Option<(usize, usize)> {
let start = self.offset() as usize;
let len = self.live_data_len();
match start.checked_add(len) {
Some(end) => Some((start, end)),
None => None,
}
}
#[inline(always)]
pub const fn reserved_byte_range(&self) -> Option<(usize, usize)> {
let start = self.offset() as usize;
let len = self.max_data_len();
match start.checked_add(len) {
Some(end) => Some((start, end)),
None => None,
}
}
}
pub struct SegmentTable<'a> {
data: &'a [u8],
segment_count: usize,
}
impl<'a> SegmentTable<'a> {
#[inline(always)]
pub fn from_bytes(data: &'a [u8], segment_count: usize) -> Result<Self, ProgramError> {
if segment_count > MAX_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
let table_size = segment_count * SEGMENT_DESC_SIZE;
if data.len() < table_size {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(Self {
data: &data[..table_size],
segment_count,
})
}
#[inline(always)]
pub fn len(&self) -> usize {
self.segment_count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.segment_count == 0
}
#[inline(always)]
pub fn descriptor(&self, index: usize) -> Result<SegmentDescriptor, ProgramError> {
if index >= self.segment_count {
return Err(ProgramError::InvalidArgument);
}
let start = index * SEGMENT_DESC_SIZE;
Ok(SegmentDescriptor {
offset: [
self.data[start],
self.data[start + 1],
self.data[start + 2],
self.data[start + 3],
],
count: [self.data[start + 4], self.data[start + 5]],
capacity: [self.data[start + 6], self.data[start + 7]],
element_size: [self.data[start + 8], self.data[start + 9]],
flags: [self.data[start + 10], self.data[start + 11]],
})
}
#[inline]
pub fn validate(
&self,
account_len: usize,
expected_sizes: &[u16],
min_offset: usize,
) -> Result<(), ProgramError> {
let mut prev_end: usize = min_offset;
for i in 0..self.segment_count {
let desc = self.descriptor(i)?;
if desc.element_size() == 0 {
return Err(ProgramError::InvalidAccountData);
}
if i < expected_sizes.len() && desc.element_size() != expected_sizes[i] {
return Err(ProgramError::InvalidAccountData);
}
if desc.count() > desc.capacity() {
return Err(ProgramError::InvalidAccountData);
}
let (start, end) = desc
.reserved_byte_range()
.ok_or(ProgramError::InvalidAccountData)?;
if end > account_len {
return Err(ProgramError::AccountDataTooSmall);
}
if start < prev_end {
return Err(ProgramError::InvalidAccountData);
}
prev_end = end;
}
Ok(())
}
#[inline(always)]
pub fn byte_len(&self) -> usize {
self.segment_count * SEGMENT_DESC_SIZE
}
}
pub struct SegmentTableMut<'a> {
data: &'a mut [u8],
segment_count: usize,
}
impl<'a> SegmentTableMut<'a> {
#[inline(always)]
pub fn from_bytes(data: &'a mut [u8], segment_count: usize) -> Result<Self, ProgramError> {
if segment_count > MAX_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
let table_size = segment_count * SEGMENT_DESC_SIZE;
if data.len() < table_size {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(Self {
data: &mut data[..table_size],
segment_count,
})
}
#[inline(always)]
pub fn descriptor(&self, index: usize) -> Result<SegmentDescriptor, ProgramError> {
if index >= self.segment_count {
return Err(ProgramError::InvalidArgument);
}
let start = index * SEGMENT_DESC_SIZE;
Ok(SegmentDescriptor {
offset: [
self.data[start],
self.data[start + 1],
self.data[start + 2],
self.data[start + 3],
],
count: [self.data[start + 4], self.data[start + 5]],
capacity: [self.data[start + 6], self.data[start + 7]],
element_size: [self.data[start + 8], self.data[start + 9]],
flags: [self.data[start + 10], self.data[start + 11]],
})
}
#[inline(always)]
pub fn set_descriptor(
&mut self,
index: usize,
desc: &SegmentDescriptor,
) -> Result<(), ProgramError> {
if index >= self.segment_count {
return Err(ProgramError::InvalidArgument);
}
let start = index * SEGMENT_DESC_SIZE;
self.data[start..start + 4].copy_from_slice(&desc.offset);
self.data[start + 4..start + 6].copy_from_slice(&desc.count);
self.data[start + 6..start + 8].copy_from_slice(&desc.capacity);
self.data[start + 8..start + 10].copy_from_slice(&desc.element_size);
self.data[start + 10..start + 12].copy_from_slice(&desc.flags);
Ok(())
}
#[inline(always)]
pub fn len(&self) -> usize {
self.segment_count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.segment_count == 0
}
#[inline]
pub fn init(
data: &'a mut [u8],
data_start: u32,
specs: &[(u16, u16, u16)],
) -> Result<Self, ProgramError> {
let segment_count = specs.len();
if segment_count > MAX_SEGMENTS {
return Err(ProgramError::InvalidArgument);
}
let table_size = segment_count * SEGMENT_DESC_SIZE;
if data.len() < table_size {
return Err(ProgramError::AccountDataTooSmall);
}
let mut offset = data_start;
for (i, &(elem_size, count, capacity)) in specs.iter().enumerate() {
if count > capacity {
return Err(ProgramError::InvalidArgument);
}
let start = i * SEGMENT_DESC_SIZE;
data[start..start + 4].copy_from_slice(&offset.to_le_bytes());
data[start + 4..start + 6].copy_from_slice(&count.to_le_bytes());
data[start + 6..start + 8].copy_from_slice(&capacity.to_le_bytes());
data[start + 8..start + 10].copy_from_slice(&elem_size.to_le_bytes());
data[start + 10..start + 12].copy_from_slice(&[0, 0]); let seg_len = (capacity as u32)
.checked_mul(elem_size as u32)
.ok_or(ProgramError::ArithmeticOverflow)?;
offset = offset
.checked_add(seg_len)
.ok_or(ProgramError::ArithmeticOverflow)?;
}
Ok(Self {
data: &mut data[..table_size],
segment_count,
})
}
}
pub struct SegmentSlice<'a, T: Pod + FixedLayout> {
data: &'a [u8],
count: u16,
capacity: u16,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Pod + FixedLayout> SegmentSlice<'a, T> {
#[inline(always)]
pub fn from_descriptor(
account_data: &'a [u8],
descriptor: &SegmentDescriptor,
) -> Result<Self, ProgramError> {
if descriptor.element_size() as usize != T::SIZE {
return Err(ProgramError::InvalidAccountData);
}
if descriptor.count() > descriptor.capacity() {
return Err(ProgramError::InvalidAccountData);
}
let (start, end) = descriptor
.byte_range()
.ok_or(ProgramError::InvalidAccountData)?;
if end > account_data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(Self {
data: &account_data[start..end],
count: descriptor.count(),
capacity: descriptor.capacity(),
_marker: core::marker::PhantomData,
})
}
#[inline(always)]
pub fn len(&self) -> u16 {
self.count
}
#[inline(always)]
pub fn capacity(&self) -> u16 {
self.capacity
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.count >= self.capacity
}
#[inline(always)]
pub fn get(&self, index: u16) -> Result<&T, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = (index as usize) * T::SIZE;
#[cfg(target_os = "solana")]
{
Ok(unsafe { &*(self.data.as_ptr().add(offset) as *const T) })
}
#[cfg(not(target_os = "solana"))]
{
let ptr = self.data.as_ptr();
if (unsafe { ptr.add(offset) } as usize) % core::mem::align_of::<T>() != 0 {
return Err(ProgramError::InvalidAccountData);
}
Ok(unsafe { &*(ptr.add(offset) as *const T) })
}
}
#[inline(always)]
pub fn read(&self, index: u16) -> Result<T, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = (index as usize) * T::SIZE;
Ok(unsafe {
core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T)
})
}
#[inline(always)]
pub fn iter(&self) -> SegmentIter<'a, T> {
SegmentIter {
data: self.data,
index: 0,
count: self.count,
_marker: core::marker::PhantomData,
}
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
self.data
}
}
pub struct SegmentSliceMut<'a, T: Pod + FixedLayout> {
data: &'a mut [u8],
count: u16,
capacity: u16,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Pod + FixedLayout> SegmentSliceMut<'a, T> {
#[inline(always)]
pub fn from_descriptor(
account_data: &'a mut [u8],
descriptor: &SegmentDescriptor,
) -> Result<Self, ProgramError> {
if descriptor.element_size() as usize != T::SIZE {
return Err(ProgramError::InvalidAccountData);
}
if descriptor.count() > descriptor.capacity() {
return Err(ProgramError::InvalidAccountData);
}
let (start, end) = descriptor
.byte_range()
.ok_or(ProgramError::InvalidAccountData)?;
if end > account_data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(Self {
data: &mut account_data[start..end],
count: descriptor.count(),
capacity: descriptor.capacity(),
_marker: core::marker::PhantomData,
})
}
#[inline(always)]
pub fn len(&self) -> u16 {
self.count
}
#[inline(always)]
pub fn capacity(&self) -> u16 {
self.capacity
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.count >= self.capacity
}
#[inline(always)]
pub fn get_mut(&mut self, index: u16) -> Result<&mut T, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = (index as usize) * T::SIZE;
#[cfg(target_os = "solana")]
{
Ok(unsafe { &mut *(self.data.as_mut_ptr().add(offset) as *mut T) })
}
#[cfg(not(target_os = "solana"))]
{
let ptr = self.data.as_mut_ptr();
if (unsafe { ptr.add(offset) } as usize) % core::mem::align_of::<T>() != 0 {
return Err(ProgramError::InvalidAccountData);
}
Ok(unsafe { &mut *(ptr.add(offset) as *mut T) })
}
}
#[inline(always)]
pub fn get(&self, index: u16) -> Result<&T, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = (index as usize) * T::SIZE;
#[cfg(target_os = "solana")]
{
Ok(unsafe { &*(self.data.as_ptr().add(offset) as *const T) })
}
#[cfg(not(target_os = "solana"))]
{
let ptr = self.data.as_ptr();
if (unsafe { ptr.add(offset) } as usize) % core::mem::align_of::<T>() != 0 {
return Err(ProgramError::InvalidAccountData);
}
Ok(unsafe { &*(ptr.add(offset) as *const T) })
}
}
#[inline(always)]
pub fn set(&mut self, index: u16, value: &T) -> Result<(), ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = (index as usize) * T::SIZE;
let src = value as *const T as *const u8;
unsafe {
core::ptr::copy_nonoverlapping(
src,
self.data.as_mut_ptr().add(offset),
T::SIZE,
);
}
Ok(())
}
#[inline(always)]
pub fn read(&self, index: u16) -> Result<T, ProgramError> {
if index >= self.count {
return Err(ProgramError::InvalidArgument);
}
let offset = (index as usize) * T::SIZE;
Ok(unsafe {
core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T)
})
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
self.data
}
}
pub struct SegmentIter<'a, T: Pod + FixedLayout> {
data: &'a [u8],
index: u16,
count: u16,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Pod + FixedLayout> Iterator for SegmentIter<'a, T> {
type Item = T;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.count {
return None;
}
let offset = (self.index as usize) * T::SIZE;
self.index += 1;
Some(unsafe {
core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T)
})
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.count - self.index) as usize;
(remaining, Some(remaining))
}
}
impl<'a, T: Pod + FixedLayout> ExactSizeIterator for SegmentIter<'a, T> {}
#[inline]
pub fn segment_push<T: Pod + FixedLayout>(
data: &mut [u8],
table_offset: usize,
segment_count: usize,
seg_index: usize,
value: &T,
) -> Result<(), ProgramError> {
let desc = {
let table = SegmentTable::from_bytes(&data[table_offset..], segment_count)?;
table.descriptor(seg_index)?
};
if desc.element_size() as usize != T::SIZE {
return Err(ProgramError::InvalidAccountData);
}
let current_count = desc.count();
if current_count >= desc.capacity() {
return Err(ProgramError::InvalidAccountData);
}
let write_offset = desc.offset() as usize + (current_count as usize) * T::SIZE;
let write_end = write_offset + T::SIZE;
if write_end > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let src = value as *const T as *const u8;
unsafe {
core::ptr::copy_nonoverlapping(src, data.as_mut_ptr().add(write_offset), T::SIZE);
}
let new_count = current_count
.checked_add(1)
.ok_or(ProgramError::ArithmeticOverflow)?;
let updated = SegmentDescriptor::new(
desc.offset(), new_count, desc.capacity(), desc.element_size(),
);
let mut table_mut = SegmentTableMut::from_bytes(&mut data[table_offset..], segment_count)?;
table_mut.set_descriptor(seg_index, &updated)?;
Ok(())
}
#[inline]
pub fn segment_swap_remove<T: Pod + FixedLayout>(
data: &mut [u8],
table_offset: usize,
segment_count: usize,
seg_index: usize,
index: u16,
) -> Result<T, ProgramError> {
let desc = {
let table = SegmentTable::from_bytes(&data[table_offset..], segment_count)?;
table.descriptor(seg_index)?
};
if desc.element_size() as usize != T::SIZE {
return Err(ProgramError::InvalidAccountData);
}
let count = desc.count();
if index >= count {
return Err(ProgramError::InvalidArgument);
}
let base = desc.offset() as usize;
let target_offset = base + (index as usize) * T::SIZE;
let removed = unsafe {
core::ptr::read_unaligned(data.as_ptr().add(target_offset) as *const T)
};
let last_index = count - 1;
if index < last_index {
let last_offset = base + (last_index as usize) * T::SIZE;
data.copy_within(last_offset..last_offset + T::SIZE, target_offset);
}
let last_offset = base + (last_index as usize) * T::SIZE;
data[last_offset..last_offset + T::SIZE].fill(0);
let updated = SegmentDescriptor::new(
desc.offset(), last_index, desc.capacity(), desc.element_size(),
);
let mut table_mut = SegmentTableMut::from_bytes(&mut data[table_offset..], segment_count)?;
table_mut.set_descriptor(seg_index, &updated)?;
Ok(removed)
}