Skip to main content

jiminy_core/account/
header.rs

1//! Zero-copy account header convention.
2//!
3//! Defines the canonical 16-byte header for Jiminy account layouts:
4//!
5//! ```text
6//! ┌────────────┬─────────┬───────┬────────────────────┬──────────────┐
7//! │ disc (1B)  │ ver (1B)│ flags │  layout_id         │  reserved    │
8//! │   u8       │   u8    │ u16   │  [u8; 8]           │  [u8; 4]     │
9//! └────────────┴─────────┴───────┴────────────────────┴──────────────┘
10//! ```
11//!
12//! Programs that adopt this header can use a single [`check_header`] call
13//! to validate discriminator + version + layout_id in one shot, and
14//! [`header_payload`] to get the body slice after the header.
15
16use pinocchio::error::ProgramError;
17
18/// Canonical account header size in bytes.
19pub const HEADER_LEN: usize = 16;
20
21/// Header format version. Tracks the header byte layout itself.
22///
23/// If the header format ever changes (field reordering, new mandatory
24/// fields, etc.), bump this constant. All current Jiminy headers use
25/// format version 1.
26pub const HEADER_FORMAT: u8 = 1;
27
28/// The canonical Jiminy account header.
29///
30/// All fields are little-endian on wire. The struct is `#[repr(C)]` so
31/// its layout matches the on-chain byte representation exactly.
32#[repr(C)]
33#[derive(Clone, Copy)]
34pub struct AccountHeader {
35    pub discriminator: u8,
36    pub version: u8,
37    pub flags: u16,
38    pub layout_id: [u8; 8],
39    pub reserved: [u8; 4],
40}
41
42impl AccountHeader {
43    /// Read an `AccountHeader` from the first 16 bytes of `data`.
44    #[inline(always)]
45    pub fn from_bytes(data: &[u8]) -> Result<&Self, ProgramError> {
46        if data.len() < HEADER_LEN {
47            return Err(ProgramError::AccountDataTooSmall);
48        }
49        // SAFETY: AccountHeader is repr(C), Copy, and 16 bytes. We checked length.
50        // All bit patterns are valid for u8/u16/[u8;8]/[u8;4].
51        Ok(unsafe { &*(data.as_ptr() as *const Self) })
52    }
53
54    /// Get a mutable reference to the header in `data`.
55    #[inline(always)]
56    pub fn from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, ProgramError> {
57        if data.len() < HEADER_LEN {
58            return Err(ProgramError::AccountDataTooSmall);
59        }
60        Ok(unsafe { &mut *(data.as_mut_ptr() as *mut Self) })
61    }
62}
63
64/// Write a full header (discriminator + version + layout_id, flags = 0, reserved = 0).
65#[inline(always)]
66pub fn write_header(
67    data: &mut [u8],
68    discriminator: u8,
69    version: u8,
70    layout_id: &[u8; 8],
71) -> Result<(), ProgramError> {
72    if data.len() < HEADER_LEN {
73        return Err(ProgramError::AccountDataTooSmall);
74    }
75    data[0] = discriminator;
76    data[1] = version;
77    // flags = 0
78    data[2] = 0;
79    data[3] = 0;
80    // layout_id
81    data[4] = layout_id[0];
82    data[5] = layout_id[1];
83    data[6] = layout_id[2];
84    data[7] = layout_id[3];
85    data[8] = layout_id[4];
86    data[9] = layout_id[5];
87    data[10] = layout_id[6];
88    data[11] = layout_id[7];
89    // reserved = 0
90    data[12] = 0;
91    data[13] = 0;
92    data[14] = 0;
93    data[15] = 0;
94    Ok(())
95}
96
97/// Validate discriminator, minimum version, and layout_id in one call.
98#[inline(always)]
99pub fn check_header(
100    data: &[u8],
101    expected_discriminator: u8,
102    min_version: u8,
103    layout_id: &[u8; 8],
104) -> Result<(), ProgramError> {
105    if data.len() < HEADER_LEN {
106        return Err(ProgramError::AccountDataTooSmall);
107    }
108    if data[0] != expected_discriminator {
109        return Err(ProgramError::InvalidAccountData);
110    }
111    if data[1] < min_version {
112        return Err(ProgramError::InvalidAccountData);
113    }
114    if data[4..12] != *layout_id {
115        return Err(ProgramError::InvalidAccountData);
116    }
117    Ok(())
118}
119
120/// Read the version byte from account data.
121#[inline(always)]
122pub fn read_version(data: &[u8]) -> Result<u8, ProgramError> {
123    if data.len() < 2 {
124        return Err(ProgramError::AccountDataTooSmall);
125    }
126    Ok(data[1])
127}
128
129/// Read the flags field (bytes 2..4) as u16 LE.
130#[inline(always)]
131pub fn read_header_flags(data: &[u8]) -> Result<u16, ProgramError> {
132    if data.len() < 4 {
133        return Err(ProgramError::AccountDataTooSmall);
134    }
135    Ok(u16::from_le_bytes([data[2], data[3]]))
136}
137
138/// Read the layout_id field (bytes 4..12).
139#[inline(always)]
140pub fn read_layout_id(data: &[u8]) -> Result<[u8; 8], ProgramError> {
141    if data.len() < 12 {
142        return Err(ProgramError::AccountDataTooSmall);
143    }
144    let mut id = [0u8; 8];
145    id.copy_from_slice(&data[4..12]);
146    Ok(id)
147}
148
149/// Validate only the layout_id at bytes 4..12.
150#[inline(always)]
151pub fn check_layout_id(data: &[u8], expected: &[u8; 8]) -> Result<(), ProgramError> {
152    if data.len() < 12 {
153        return Err(ProgramError::AccountDataTooSmall);
154    }
155    if data[4..12] != *expected {
156        return Err(ProgramError::InvalidAccountData);
157    }
158    Ok(())
159}
160
161/// Return the body slice after the 16-byte header.
162#[inline(always)]
163pub fn header_payload(data: &[u8]) -> Result<&[u8], ProgramError> {
164    if data.len() < HEADER_LEN {
165        return Err(ProgramError::AccountDataTooSmall);
166    }
167    Ok(&data[HEADER_LEN..])
168}
169
170/// Return the mutable body slice after the 16-byte header.
171#[inline(always)]
172pub fn header_payload_mut(data: &mut [u8]) -> Result<&mut [u8], ProgramError> {
173    if data.len() < HEADER_LEN {
174        return Err(ProgramError::AccountDataTooSmall);
175    }
176    Ok(&mut data[HEADER_LEN..])
177}
178
179/// Return the body slice (alias: everything after the header).
180#[inline(always)]
181pub fn body(data: &[u8]) -> Result<&[u8], ProgramError> {
182    header_payload(data)
183}
184
185/// Return the mutable body slice.
186#[inline(always)]
187pub fn body_mut(data: &mut [u8]) -> Result<&mut [u8], ProgramError> {
188    header_payload_mut(data)
189}
190
191// ── Pod / FixedLayout ────────────────────────────────────────────────────────
192
193// SAFETY: AccountHeader is #[repr(C)], Copy, 16 bytes, and all bit patterns
194// are valid for u8 / u16 / [u8; 8] / [u8; 4].
195unsafe impl super::pod::Pod for AccountHeader {}
196
197impl super::pod::FixedLayout for AccountHeader {
198    const SIZE: usize = HEADER_LEN;
199}