Skip to main content

jiminy_core/account/
reader.rs

1//! Zero-copy account reader with header awareness.
2
3use pinocchio::error::ProgramError;
4
5use super::cursor::SliceCursor;
6use super::header::{AccountHeader, HEADER_LEN};
7
8/// Zero-copy account reader with header awareness.
9///
10/// Constructs from a borrowed `&[u8]`, validates the header, and exposes
11/// a cursor over the body for sequential field reads.
12pub struct AccountReader<'a> {
13    data: &'a [u8],
14    body_offset: usize,
15}
16
17impl<'a> AccountReader<'a> {
18    /// Create a new reader, validating that the data is at least `HEADER_LEN` bytes.
19    #[inline(always)]
20    pub fn new(data: &'a [u8]) -> Result<Self, ProgramError> {
21        if data.len() < HEADER_LEN {
22            return Err(ProgramError::AccountDataTooSmall);
23        }
24        Ok(Self {
25            data,
26            body_offset: HEADER_LEN,
27        })
28    }
29
30    /// Create a reader and validate discriminator + minimum version + layout_id.
31    #[inline(always)]
32    pub fn new_checked(
33        data: &'a [u8],
34        expected_disc: u8,
35        min_version: u8,
36        layout_id: &[u8; 8],
37    ) -> Result<Self, ProgramError> {
38        if data.len() < HEADER_LEN {
39            return Err(ProgramError::AccountDataTooSmall);
40        }
41        if data[0] != expected_disc {
42            return Err(ProgramError::InvalidAccountData);
43        }
44        if data[1] < min_version {
45            return Err(ProgramError::InvalidAccountData);
46        }
47        if data[4..12] != *layout_id {
48            return Err(ProgramError::InvalidAccountData);
49        }
50        Ok(Self {
51            data,
52            body_offset: HEADER_LEN,
53        })
54    }
55
56    /// Read the header as a typed reference.
57    #[inline(always)]
58    pub fn header(&self) -> &AccountHeader {
59        // SAFETY: We checked length in new().
60        unsafe { &*(self.data.as_ptr() as *const AccountHeader) }
61    }
62
63    /// Get the discriminator byte.
64    #[inline(always)]
65    pub fn discriminator(&self) -> u8 {
66        self.data[0]
67    }
68
69    /// Get the version byte.
70    #[inline(always)]
71    pub fn version(&self) -> u8 {
72        self.data[1]
73    }
74
75    /// Get the flags field.
76    #[inline(always)]
77    pub fn flags(&self) -> u16 {
78        u16::from_le_bytes([self.data[2], self.data[3]])
79    }
80
81    /// Get the layout_id field (bytes 4..12).
82    #[inline(always)]
83    pub fn layout_id(&self) -> [u8; 8] {
84        let mut id = [0u8; 8];
85        id.copy_from_slice(&self.data[4..12]);
86        id
87    }
88
89    /// Get a cursor positioned at the start of the body (after the header).
90    #[inline(always)]
91    pub fn body(&self) -> SliceCursor<'a> {
92        SliceCursor::new(&self.data[self.body_offset..])
93    }
94
95    /// Get the raw body bytes.
96    #[inline(always)]
97    pub fn body_bytes(&self) -> &'a [u8] {
98        &self.data[self.body_offset..]
99    }
100
101    /// Get the full raw data including header.
102    #[inline(always)]
103    pub fn raw(&self) -> &'a [u8] {
104        self.data
105    }
106
107    /// Read a pubkey from the body at a given byte offset.
108    #[inline(always)]
109    pub fn pubkey_at(&self, offset: usize) -> Result<&'a [u8; 32], ProgramError> {
110        let abs = self.body_offset + offset;
111        if abs + 32 > self.data.len() {
112            return Err(ProgramError::AccountDataTooSmall);
113        }
114        // SAFETY: bounds checked above.
115        Ok(unsafe { &*(self.data.as_ptr().add(abs) as *const [u8; 32]) })
116    }
117
118    /// Read a u64 from the body at a given byte offset.
119    #[inline(always)]
120    pub fn u64_at(&self, offset: usize) -> Result<u64, ProgramError> {
121        let abs = self.body_offset + offset;
122        if abs + 8 > self.data.len() {
123            return Err(ProgramError::AccountDataTooSmall);
124        }
125        Ok(u64::from_le_bytes([
126            self.data[abs],
127            self.data[abs + 1],
128            self.data[abs + 2],
129            self.data[abs + 3],
130            self.data[abs + 4],
131            self.data[abs + 5],
132            self.data[abs + 6],
133            self.data[abs + 7],
134        ]))
135    }
136
137    /// Read a u32 from the body at a given byte offset.
138    #[inline(always)]
139    pub fn u32_at(&self, offset: usize) -> Result<u32, ProgramError> {
140        let abs = self.body_offset + offset;
141        if abs + 4 > self.data.len() {
142            return Err(ProgramError::AccountDataTooSmall);
143        }
144        Ok(u32::from_le_bytes([
145            self.data[abs],
146            self.data[abs + 1],
147            self.data[abs + 2],
148            self.data[abs + 3],
149        ]))
150    }
151
152    /// Read a u16 from the body at a given byte offset.
153    #[inline(always)]
154    pub fn u16_at(&self, offset: usize) -> Result<u16, ProgramError> {
155        let abs = self.body_offset + offset;
156        if abs + 2 > self.data.len() {
157            return Err(ProgramError::AccountDataTooSmall);
158        }
159        Ok(u16::from_le_bytes([self.data[abs], self.data[abs + 1]]))
160    }
161
162    /// Read a u8 from the body at a given byte offset.
163    #[inline(always)]
164    pub fn u8_at(&self, offset: usize) -> Result<u8, ProgramError> {
165        let abs = self.body_offset + offset;
166        if abs >= self.data.len() {
167            return Err(ProgramError::AccountDataTooSmall);
168        }
169        Ok(self.data[abs])
170    }
171
172    /// Read an i64 from the body at a given byte offset.
173    #[inline(always)]
174    pub fn i64_at(&self, offset: usize) -> Result<i64, ProgramError> {
175        let abs = self.body_offset + offset;
176        if abs + 8 > self.data.len() {
177            return Err(ProgramError::AccountDataTooSmall);
178        }
179        Ok(i64::from_le_bytes([
180            self.data[abs],
181            self.data[abs + 1],
182            self.data[abs + 2],
183            self.data[abs + 3],
184            self.data[abs + 4],
185            self.data[abs + 5],
186            self.data[abs + 6],
187            self.data[abs + 7],
188        ]))
189    }
190
191    /// Read a bool from the body at a given byte offset.
192    #[inline(always)]
193    pub fn bool_at(&self, offset: usize) -> Result<bool, ProgramError> {
194        Ok(self.u8_at(offset)? != 0)
195    }
196
197    /// Read a fixed-size byte array from the body at a given byte offset.
198    #[inline(always)]
199    pub fn bytes_at<const N: usize>(&self, offset: usize) -> Result<[u8; N], ProgramError> {
200        let abs = self.body_offset + offset;
201        if abs + N > self.data.len() {
202            return Err(ProgramError::AccountDataTooSmall);
203        }
204        let mut out = [0u8; N];
205        out.copy_from_slice(&self.data[abs..abs + N]);
206        Ok(out)
207    }
208
209    /// Number of body bytes remaining after the header.
210    #[inline(always)]
211    pub fn body_len(&self) -> usize {
212        self.data.len().saturating_sub(self.body_offset)
213    }
214}