1use hopper_schema::{FieldDescriptor, LayoutManifest};
19
20use crate::fingerprint::{
21 check_against_layout, FingerprintCheck, FingerprintError, LAYOUT_ID_OFFSET,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ReaderError {
27 BufferTooShort {
29 required: usize,
31 got: usize,
33 },
34 LayoutMismatch {
36 expected: [u8; 8],
38 actual: [u8; 8],
40 },
41 UnknownField,
43 SizeMismatch {
45 wire: u16,
47 requested: usize,
49 },
50 Fingerprint(FingerprintError),
52}
53
54impl From<FingerprintError> for ReaderError {
55 fn from(e: FingerprintError) -> Self {
56 ReaderError::Fingerprint(e)
57 }
58}
59
60#[derive(Debug)]
65pub struct SegmentReader<'a> {
66 bytes: &'a [u8],
67 layout: &'a LayoutManifest,
68}
69
70impl<'a> SegmentReader<'a> {
71 pub fn new(bytes: &'a [u8], layout: &'a LayoutManifest) -> Result<Self, ReaderError> {
73 if bytes.len() < layout.total_size {
74 return Err(ReaderError::BufferTooShort {
75 required: layout.total_size,
76 got: bytes.len(),
77 });
78 }
79 match check_against_layout(bytes, layout)? {
80 FingerprintCheck::Match => Ok(Self { bytes, layout }),
81 FingerprintCheck::Mismatch { expected, actual } => {
82 Err(ReaderError::LayoutMismatch { expected, actual })
83 }
84 }
85 }
86
87 pub unsafe fn new_unchecked(bytes: &'a [u8], layout: &'a LayoutManifest) -> Self {
93 Self { bytes, layout }
94 }
95
96 pub const fn bytes(&self) -> &'a [u8] {
98 self.bytes
99 }
100
101 pub const fn layout(&self) -> &'a LayoutManifest {
103 self.layout
104 }
105
106 pub fn field(&self, name: &str) -> Option<&'a FieldDescriptor> {
108 let mut i = 0;
109 while i < self.layout.fields.len() {
110 if bytes_eq(self.layout.fields[i].name, name) {
111 return Some(&self.layout.fields[i]);
112 }
113 i += 1;
114 }
115 None
116 }
117
118 pub fn offset_of(&self, name: &str) -> Option<usize> {
123 self.field(name).map(|f| f.offset as usize)
124 }
125
126 pub fn read_raw(&self, name: &str) -> Result<&'a [u8], ReaderError> {
128 let f = self.field(name).ok_or(ReaderError::UnknownField)?;
129 let start = f.offset as usize;
130 let end = start + f.size as usize;
131 if end > self.bytes.len() {
132 return Err(ReaderError::BufferTooShort {
133 required: end,
134 got: self.bytes.len(),
135 });
136 }
137 Ok(&self.bytes[start..end])
138 }
139
140 pub fn read_u8(&self, name: &str) -> Result<u8, ReaderError> {
142 let raw = self.read_fixed(name, 1)?;
143 Ok(raw[0])
144 }
145
146 pub fn read_u16(&self, name: &str) -> Result<u16, ReaderError> {
148 let raw = self.read_fixed(name, 2)?;
149 Ok(u16::from_le_bytes([raw[0], raw[1]]))
150 }
151
152 pub fn read_u32(&self, name: &str) -> Result<u32, ReaderError> {
154 let raw = self.read_fixed(name, 4)?;
155 Ok(u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]))
156 }
157
158 pub fn read_u64(&self, name: &str) -> Result<u64, ReaderError> {
160 let raw = self.read_fixed(name, 8)?;
161 Ok(u64::from_le_bytes([
162 raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
163 ]))
164 }
165
166 pub fn read_u128(&self, name: &str) -> Result<u128, ReaderError> {
168 let raw = self.read_fixed(name, 16)?;
169 let mut bytes = [0u8; 16];
170 bytes.copy_from_slice(raw);
171 Ok(u128::from_le_bytes(bytes))
172 }
173
174 pub fn read_pubkey(&self, name: &str) -> Result<[u8; 32], ReaderError> {
176 let raw = self.read_fixed(name, 32)?;
177 let mut out = [0u8; 32];
178 out.copy_from_slice(raw);
179 Ok(out)
180 }
181
182 pub fn layout_id(&self) -> [u8; 8] {
184 let mut id = [0u8; 8];
185 id.copy_from_slice(&self.bytes[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8]);
186 id
187 }
188
189 fn read_fixed(&self, name: &str, expect: usize) -> Result<&'a [u8], ReaderError> {
190 let f = self.field(name).ok_or(ReaderError::UnknownField)?;
191 if f.size as usize != expect {
192 return Err(ReaderError::SizeMismatch {
193 wire: f.size,
194 requested: expect,
195 });
196 }
197 let start = f.offset as usize;
198 let end = start + expect;
199 if end > self.bytes.len() {
200 return Err(ReaderError::BufferTooShort {
201 required: end,
202 got: self.bytes.len(),
203 });
204 }
205 Ok(&self.bytes[start..end])
206 }
207}
208
209fn bytes_eq(a: &str, b: &str) -> bool {
210 let a = a.as_bytes();
213 let b = b.as_bytes();
214 if a.len() != b.len() {
215 return false;
216 }
217 let mut i = 0;
218 while i < a.len() {
219 if a[i] != b[i] {
220 return false;
221 }
222 i += 1;
223 }
224 true
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use hopper_schema::FieldIntent;
231
232 const LAYOUT_ID: [u8; 8] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22];
233
234 fn fields() -> &'static [FieldDescriptor] {
235 static F: [FieldDescriptor; 3] = [
236 FieldDescriptor {
237 name: "authority",
238 canonical_type: "Pubkey",
239 size: 32,
240 offset: 16,
241 intent: FieldIntent::Authority,
242 },
243 FieldDescriptor {
244 name: "balance",
245 canonical_type: "u64",
246 size: 8,
247 offset: 48,
248 intent: FieldIntent::Balance,
249 },
250 FieldDescriptor {
251 name: "bump",
252 canonical_type: "u8",
253 size: 1,
254 offset: 56,
255 intent: FieldIntent::Bump,
256 },
257 ];
258 &F
259 }
260
261 fn manifest() -> LayoutManifest {
262 LayoutManifest {
263 name: "Vault",
264 disc: 5,
265 version: 1,
266 layout_id: LAYOUT_ID,
267 total_size: 80,
268 field_count: 3,
269 fields: fields(),
270 }
271 }
272
273 fn blob() -> [u8; 80] {
274 let mut b = [0u8; 80];
275 b[0] = 5;
276 b[1] = 1;
277 b[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8].copy_from_slice(&LAYOUT_ID);
278 for i in 0..32 {
280 b[16 + i] = i as u8;
281 }
282 b[48..56].copy_from_slice(&1_000_000u64.to_le_bytes());
284 b[56] = 253;
286 b
287 }
288
289 #[test]
290 fn binds_and_reads() {
291 let m = manifest();
292 let b = blob();
293 let r = SegmentReader::new(&b, &m).unwrap();
294 assert_eq!(r.read_u64("balance").unwrap(), 1_000_000);
295 assert_eq!(r.read_u8("bump").unwrap(), 253);
296 let pk = r.read_pubkey("authority").unwrap();
297 assert_eq!(pk[0], 0);
298 assert_eq!(pk[31], 31);
299 }
300
301 #[test]
302 fn rejects_wrong_layout_id() {
303 let m = manifest();
304 let mut b = blob();
305 b[LAYOUT_ID_OFFSET] ^= 0xFF;
306 let err = SegmentReader::new(&b, &m).unwrap_err();
307 assert!(matches!(err, ReaderError::LayoutMismatch { .. }));
308 }
309
310 #[test]
311 fn rejects_too_short() {
312 let m = manifest();
313 let b = [0u8; 40];
314 let err = SegmentReader::new(&b, &m).unwrap_err();
315 assert!(matches!(
316 err,
317 ReaderError::BufferTooShort {
318 required: 80,
319 got: 40
320 }
321 ));
322 }
323
324 #[test]
325 fn size_mismatch_detected() {
326 let m = manifest();
327 let b = blob();
328 let r = SegmentReader::new(&b, &m).unwrap();
329 assert!(matches!(
330 r.read_u32("balance"),
331 Err(ReaderError::SizeMismatch {
332 wire: 8,
333 requested: 4
334 })
335 ));
336 }
337}