Skip to main content

rvf_types/
segment.rs

1//! 64-byte segment header for the RVF format.
2
3/// The fixed 64-byte header that precedes every segment payload.
4///
5/// Layout matches the wire format exactly (repr(C), little-endian fields).
6/// Aligned to 64 bytes to match SIMD register width and cache-line size.
7#[derive(Clone, Copy, Debug)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[repr(C)]
10pub struct SegmentHeader {
11    /// Magic number: must be `0x52564653` ("RVFS").
12    pub magic: u32,
13    /// Segment format version (currently 1).
14    pub version: u8,
15    /// Segment type discriminator (see `SegmentType`).
16    pub seg_type: u8,
17    /// Bitfield flags (see `SegmentFlags`).
18    pub flags: u16,
19    /// Monotonically increasing segment ordinal.
20    pub segment_id: u64,
21    /// Byte length of payload (after header, before optional footer).
22    pub payload_length: u64,
23    /// Nanosecond UNIX timestamp of segment creation.
24    pub timestamp_ns: u64,
25    /// Hash algorithm enum: 0=CRC32C, 1=XXH3-128, 2=SHAKE-256.
26    pub checksum_algo: u8,
27    /// Compression enum: 0=none, 1=LZ4, 2=ZSTD, 3=custom.
28    pub compression: u8,
29    /// Reserved (must be zero).
30    pub reserved_0: u16,
31    /// Reserved (must be zero).
32    pub reserved_1: u32,
33    /// First 128 bits of payload hash (algorithm per `checksum_algo`).
34    pub content_hash: [u8; 16],
35    /// Original payload size before compression (0 if uncompressed).
36    pub uncompressed_len: u32,
37    /// Padding to reach the 64-byte boundary.
38    pub alignment_pad: u32,
39}
40
41// Compile-time assertion: SegmentHeader must be exactly 64 bytes.
42const _: () = assert!(core::mem::size_of::<SegmentHeader>() == 64);
43
44impl SegmentHeader {
45    /// Create a new segment header with the given type and segment ID.
46    /// All other fields are set to defaults.
47    pub const fn new(seg_type: u8, segment_id: u64) -> Self {
48        Self {
49            magic: crate::constants::SEGMENT_MAGIC,
50            version: crate::constants::SEGMENT_VERSION,
51            seg_type,
52            flags: 0,
53            segment_id,
54            payload_length: 0,
55            timestamp_ns: 0,
56            checksum_algo: 0,
57            compression: 0,
58            reserved_0: 0,
59            reserved_1: 0,
60            content_hash: [0u8; 16],
61            uncompressed_len: 0,
62            alignment_pad: 0,
63        }
64    }
65
66    /// Check whether the magic field matches the expected value.
67    #[inline]
68    pub const fn is_valid_magic(&self) -> bool {
69        self.magic == crate::constants::SEGMENT_MAGIC
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::constants::SEGMENT_MAGIC;
77
78    #[test]
79    fn header_size_is_64() {
80        assert_eq!(core::mem::size_of::<SegmentHeader>(), 64);
81    }
82
83    #[test]
84    fn header_alignment() {
85        assert!(core::mem::align_of::<SegmentHeader>() <= 64);
86    }
87
88    #[test]
89    fn new_header_has_valid_magic() {
90        let h = SegmentHeader::new(0x01, 42);
91        assert!(h.is_valid_magic());
92        assert_eq!(h.magic, SEGMENT_MAGIC);
93        assert_eq!(h.seg_type, 0x01);
94        assert_eq!(h.segment_id, 42);
95    }
96
97    #[test]
98    fn field_offsets() {
99        // Verify field offsets match the wire format spec
100        let h = SegmentHeader::new(0x01, 0);
101        let base = &h as *const _ as usize;
102        let magic_off = &h.magic as *const _ as usize - base;
103        let version_off = &h.version as *const _ as usize - base;
104        let seg_type_off = &h.seg_type as *const _ as usize - base;
105        let flags_off = &h.flags as *const _ as usize - base;
106        let segment_id_off = &h.segment_id as *const _ as usize - base;
107        let payload_length_off = &h.payload_length as *const _ as usize - base;
108        let timestamp_ns_off = &h.timestamp_ns as *const _ as usize - base;
109        let checksum_algo_off = &h.checksum_algo as *const _ as usize - base;
110        let compression_off = &h.compression as *const _ as usize - base;
111        let reserved_0_off = &h.reserved_0 as *const _ as usize - base;
112        let reserved_1_off = &h.reserved_1 as *const _ as usize - base;
113        let content_hash_off = &h.content_hash as *const _ as usize - base;
114        let uncompressed_len_off = &h.uncompressed_len as *const _ as usize - base;
115        let alignment_pad_off = &h.alignment_pad as *const _ as usize - base;
116
117        assert_eq!(magic_off, 0x00);
118        assert_eq!(version_off, 0x04);
119        assert_eq!(seg_type_off, 0x05);
120        assert_eq!(flags_off, 0x06);
121        assert_eq!(segment_id_off, 0x08);
122        assert_eq!(payload_length_off, 0x10);
123        assert_eq!(timestamp_ns_off, 0x18);
124        assert_eq!(checksum_algo_off, 0x20);
125        assert_eq!(compression_off, 0x21);
126        assert_eq!(reserved_0_off, 0x22);
127        assert_eq!(reserved_1_off, 0x24);
128        assert_eq!(content_hash_off, 0x28);
129        assert_eq!(uncompressed_len_off, 0x38);
130        assert_eq!(alignment_pad_off, 0x3C);
131    }
132}