Skip to main content

rvf_types/
refcount.rs

1//! REFCOUNT_SEG (0x21) types for the RVF computational container.
2//!
3//! Defines the 32-byte `RefcountHeader` per ADR-031.
4//! The REFCOUNT_SEG tracks reference counts for shared clusters,
5//! enabling safe snapshot deletion and garbage collection.
6
7use crate::error::RvfError;
8
9/// Magic number for `RefcountHeader`: "RVRC" in big-endian.
10pub const REFCOUNT_MAGIC: u32 = 0x5256_5243;
11
12/// 32-byte header for REFCOUNT_SEG payloads.
13///
14/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
15/// little-endian on the wire.
16#[derive(Clone, Copy, Debug)]
17#[repr(C)]
18pub struct RefcountHeader {
19    /// Magic: `REFCOUNT_MAGIC` (0x52565243, "RVRC").
20    pub magic: u32,
21    /// RefcountHeader format version (currently 1).
22    pub version: u16,
23    /// Width of each refcount entry in bytes (1, 2, or 4).
24    pub refcount_width: u8,
25    /// Padding (must be zero).
26    pub _pad: u8,
27    /// Number of clusters tracked.
28    pub cluster_count: u32,
29    /// Maximum refcount value before overflow.
30    pub max_refcount: u32,
31    /// Offset to the refcount array within the segment payload.
32    pub array_offset: u64,
33    /// Snapshot epoch: 0 = mutable, >0 = frozen at this epoch.
34    pub snapshot_epoch: u32,
35    /// Reserved (must be zero).
36    pub _reserved: u32,
37}
38
39// Compile-time assertion: RefcountHeader must be exactly 32 bytes.
40const _: () = assert!(core::mem::size_of::<RefcountHeader>() == 32);
41
42impl RefcountHeader {
43    /// Serialize the header to a 32-byte little-endian array.
44    pub fn to_bytes(&self) -> [u8; 32] {
45        let mut buf = [0u8; 32];
46        buf[0x00..0x04].copy_from_slice(&self.magic.to_le_bytes());
47        buf[0x04..0x06].copy_from_slice(&self.version.to_le_bytes());
48        buf[0x06] = self.refcount_width;
49        buf[0x07] = self._pad;
50        buf[0x08..0x0C].copy_from_slice(&self.cluster_count.to_le_bytes());
51        buf[0x0C..0x10].copy_from_slice(&self.max_refcount.to_le_bytes());
52        buf[0x10..0x18].copy_from_slice(&self.array_offset.to_le_bytes());
53        buf[0x18..0x1C].copy_from_slice(&self.snapshot_epoch.to_le_bytes());
54        buf[0x1C..0x20].copy_from_slice(&self._reserved.to_le_bytes());
55        buf
56    }
57
58    /// Deserialize a `RefcountHeader` from a 32-byte slice.
59    pub fn from_bytes(data: &[u8; 32]) -> Result<Self, RvfError> {
60        let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
61        if magic != REFCOUNT_MAGIC {
62            return Err(RvfError::BadMagic {
63                expected: REFCOUNT_MAGIC,
64                got: magic,
65            });
66        }
67
68        let refcount_width = data[0x06];
69        let pad = data[0x07];
70        let reserved = u32::from_le_bytes([data[0x1C], data[0x1D], data[0x1E], data[0x1F]]);
71
72        // Validate refcount_width is 1, 2, or 4 as specified
73        if refcount_width != 1 && refcount_width != 2 && refcount_width != 4 {
74            return Err(RvfError::InvalidEnumValue {
75                type_name: "RefcountHeader::refcount_width",
76                value: refcount_width as u64,
77            });
78        }
79
80        // Validate padding and reserved fields are zero (spec requirement)
81        if pad != 0 {
82            return Err(RvfError::InvalidEnumValue {
83                type_name: "RefcountHeader::_pad",
84                value: pad as u64,
85            });
86        }
87        if reserved != 0 {
88            return Err(RvfError::InvalidEnumValue {
89                type_name: "RefcountHeader::_reserved",
90                value: reserved as u64,
91            });
92        }
93
94        Ok(Self {
95            magic,
96            version: u16::from_le_bytes([data[0x04], data[0x05]]),
97            refcount_width,
98            _pad: pad,
99            cluster_count: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
100            max_refcount: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
101            array_offset: u64::from_le_bytes([
102                data[0x10], data[0x11], data[0x12], data[0x13],
103                data[0x14], data[0x15], data[0x16], data[0x17],
104            ]),
105            snapshot_epoch: u32::from_le_bytes([data[0x18], data[0x19], data[0x1A], data[0x1B]]),
106            _reserved: reserved,
107        })
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    fn sample_header() -> RefcountHeader {
116        RefcountHeader {
117            magic: REFCOUNT_MAGIC,
118            version: 1,
119            refcount_width: 2,
120            _pad: 0,
121            cluster_count: 1024,
122            max_refcount: 65535,
123            array_offset: 64,
124            snapshot_epoch: 0,
125            _reserved: 0,
126        }
127    }
128
129    #[test]
130    fn header_size_is_32() {
131        assert_eq!(core::mem::size_of::<RefcountHeader>(), 32);
132    }
133
134    #[test]
135    fn magic_bytes_match_ascii() {
136        let bytes_be = REFCOUNT_MAGIC.to_be_bytes();
137        assert_eq!(&bytes_be, b"RVRC");
138    }
139
140    #[test]
141    fn round_trip_serialization() {
142        let original = sample_header();
143        let bytes = original.to_bytes();
144        let decoded = RefcountHeader::from_bytes(&bytes).expect("from_bytes should succeed");
145
146        assert_eq!(decoded.magic, REFCOUNT_MAGIC);
147        assert_eq!(decoded.version, 1);
148        assert_eq!(decoded.refcount_width, 2);
149        assert_eq!(decoded._pad, 0);
150        assert_eq!(decoded.cluster_count, 1024);
151        assert_eq!(decoded.max_refcount, 65535);
152        assert_eq!(decoded.array_offset, 64);
153        assert_eq!(decoded.snapshot_epoch, 0);
154        assert_eq!(decoded._reserved, 0);
155    }
156
157    #[test]
158    fn bad_magic_returns_error() {
159        let mut bytes = sample_header().to_bytes();
160        bytes[0] = 0x00; // corrupt magic
161        let err = RefcountHeader::from_bytes(&bytes).unwrap_err();
162        match err {
163            RvfError::BadMagic { expected, .. } => assert_eq!(expected, REFCOUNT_MAGIC),
164            other => panic!("expected BadMagic, got {other:?}"),
165        }
166    }
167
168    #[test]
169    fn field_offsets() {
170        let h = sample_header();
171        let base = &h as *const _ as usize;
172
173        assert_eq!(&h.magic as *const _ as usize - base, 0x00);
174        assert_eq!(&h.version as *const _ as usize - base, 0x04);
175        assert_eq!(&h.refcount_width as *const _ as usize - base, 0x06);
176        assert_eq!(&h._pad as *const _ as usize - base, 0x07);
177        assert_eq!(&h.cluster_count as *const _ as usize - base, 0x08);
178        assert_eq!(&h.max_refcount as *const _ as usize - base, 0x0C);
179        assert_eq!(&h.array_offset as *const _ as usize - base, 0x10);
180        assert_eq!(&h.snapshot_epoch as *const _ as usize - base, 0x18);
181        assert_eq!(&h._reserved as *const _ as usize - base, 0x1C);
182    }
183
184    #[test]
185    fn frozen_snapshot_epoch() {
186        let mut h = sample_header();
187        h.snapshot_epoch = 42;
188        let bytes = h.to_bytes();
189        let decoded = RefcountHeader::from_bytes(&bytes).unwrap();
190        assert_eq!(decoded.snapshot_epoch, 42);
191    }
192}