use crate::account::{FixedLayout, Pod};
use hopper_runtime::error::ProgramError;
pub const JOURNAL_HEADER_SIZE: usize = 16;
pub const JOURNAL_FLAG_CIRCULAR: u32 = 1 << 0;
pub struct Journal<'a, T: Pod + FixedLayout> {
data: &'a mut [u8],
capacity: usize,
_phantom: core::marker::PhantomData<T>,
}
impl<'a, T: Pod + FixedLayout> Journal<'a, T> {
#[inline]
pub fn from_bytes_mut(data: &'a mut [u8]) -> Result<Self, ProgramError> {
if data.len() < JOURNAL_HEADER_SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
let usable = data.len() - JOURNAL_HEADER_SIZE;
if T::SIZE == 0 {
return Err(ProgramError::InvalidArgument);
}
let capacity = usable / T::SIZE;
Ok(Self {
data,
capacity,
_phantom: core::marker::PhantomData,
})
}
#[inline]
pub fn from_bytes(data: &[u8]) -> Result<JournalReader<'_, T>, ProgramError> {
if data.len() < JOURNAL_HEADER_SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
let usable = data.len() - JOURNAL_HEADER_SIZE;
if T::SIZE == 0 {
return Err(ProgramError::InvalidArgument);
}
let capacity = usable / T::SIZE;
Ok(JournalReader {
data,
capacity,
_phantom: core::marker::PhantomData,
})
}
#[inline(always)]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline(always)]
pub fn write_head(&self) -> u32 {
u32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]])
}
#[inline(always)]
pub fn total_written(&self) -> u32 {
u32::from_le_bytes([self.data[4], self.data[5], self.data[6], self.data[7]])
}
#[inline(always)]
pub fn flags(&self) -> u32 {
u32::from_le_bytes([self.data[8], self.data[9], self.data[10], self.data[11]])
}
#[inline(always)]
pub fn is_circular(&self) -> bool {
self.flags() & JOURNAL_FLAG_CIRCULAR != 0
}
#[inline(always)]
pub fn has_wrapped(&self) -> bool {
(self.total_written() as usize) > self.capacity
}
#[inline(always)]
pub fn entry_count(&self) -> usize {
let total = self.total_written() as usize;
if total < self.capacity {
total
} else {
self.capacity
}
}
#[inline]
pub fn append(&mut self, entry: T) -> Result<(), ProgramError> {
let mut head = self.write_head() as usize;
if head >= self.capacity {
if !self.is_circular() {
return Err(ProgramError::AccountDataTooSmall);
}
head %= self.capacity;
}
if !self.is_circular() && head >= self.capacity {
return Err(ProgramError::AccountDataTooSmall);
}
let offset = JOURNAL_HEADER_SIZE + head * T::SIZE;
let end = offset + T::SIZE;
if end > self.data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
unsafe {
core::ptr::copy_nonoverlapping(
&entry as *const T as *const u8,
self.data.as_mut_ptr().add(offset),
T::SIZE,
);
}
let new_head = if self.is_circular() {
((head + 1) % self.capacity) as u32
} else {
(head + 1) as u32
};
self.set_write_head(new_head);
let total = self.total_written().wrapping_add(1);
self.set_total_written(total);
Ok(())
}
#[inline]
pub fn read(&self, index: usize) -> Result<T, ProgramError> {
let count = self.entry_count();
if index >= count {
return Err(ProgramError::InvalidArgument);
}
let physical = if self.total_written() as usize > self.capacity {
(self.write_head() as usize + index) % self.capacity
} else {
index
};
let offset = JOURNAL_HEADER_SIZE + physical * T::SIZE;
let end = offset + T::SIZE;
if end > self.data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(unsafe { core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T) })
}
#[inline]
pub fn latest(&self) -> Result<T, ProgramError> {
let count = self.entry_count();
if count == 0 {
return Err(ProgramError::InvalidArgument);
}
self.read(count - 1)
}
#[inline(always)]
pub const fn required_bytes(capacity: usize) -> usize {
JOURNAL_HEADER_SIZE + capacity * T::SIZE
}
#[inline]
pub fn init(&mut self, circular: bool) {
self.set_write_head(0);
self.set_total_written(0);
let flags: u32 = if circular { JOURNAL_FLAG_CIRCULAR } else { 0 };
self.data[8..12].copy_from_slice(&flags.to_le_bytes());
self.data[12..16].copy_from_slice(&0u32.to_le_bytes());
}
#[inline(always)]
fn set_write_head(&mut self, head: u32) {
self.data[0..4].copy_from_slice(&head.to_le_bytes());
}
#[inline(always)]
fn set_total_written(&mut self, total: u32) {
self.data[4..8].copy_from_slice(&total.to_le_bytes());
}
}
pub struct JournalReader<'a, T: Pod + FixedLayout> {
data: &'a [u8],
capacity: usize,
_phantom: core::marker::PhantomData<T>,
}
impl<'a, T: Pod + FixedLayout> JournalReader<'a, T> {
#[inline(always)]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline(always)]
pub fn write_head(&self) -> u32 {
u32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]])
}
#[inline(always)]
pub fn total_written(&self) -> u32 {
u32::from_le_bytes([self.data[4], self.data[5], self.data[6], self.data[7]])
}
#[inline(always)]
pub fn entry_count(&self) -> usize {
let total = self.total_written() as usize;
if total < self.capacity {
total
} else {
self.capacity
}
}
#[inline(always)]
pub fn is_circular(&self) -> bool {
let flags = u32::from_le_bytes([self.data[8], self.data[9], self.data[10], self.data[11]]);
flags & JOURNAL_FLAG_CIRCULAR != 0
}
#[inline]
pub fn read(&self, index: usize) -> Result<T, ProgramError> {
let count = self.entry_count();
if index >= count {
return Err(ProgramError::InvalidArgument);
}
let physical = if self.total_written() as usize > self.capacity {
(self.write_head() as usize + index) % self.capacity
} else {
index
};
let offset = JOURNAL_HEADER_SIZE + physical * T::SIZE;
let end = offset + T::SIZE;
if end > self.data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(unsafe { core::ptr::read_unaligned(self.data.as_ptr().add(offset) as *const T) })
}
#[inline]
pub fn latest(&self) -> Result<T, ProgramError> {
let count = self.entry_count();
if count == 0 {
return Err(ProgramError::InvalidArgument);
}
self.read(count - 1)
}
}