Skip to main content

ryg_rans_rs_cli/container/
header.rs

1//! # File header — fixed-size, exactly 32 bytes.
2
3use crate::container::{HEADER_SIZE, MAGIC, MAJOR_VERSION, MINOR_VERSION};
4use crate::error::{AppError, FormatError};
5
6/// Canonical file header flags bitfield.
7pub mod flags {
8    pub const GLOBAL_MODEL: u16 = 0x0001;
9    pub const ALWAYS_COMPRESS: u16 = 0x0002;
10    pub const HAS_EXTRA_HEADER: u16 = 0x0004;
11    /// Mask of all defined flags.
12    pub const KNOWN_FLAGS: u16 = GLOBAL_MODEL | ALWAYS_COMPRESS | HAS_EXTRA_HEADER;
13}
14
15/// Codec-independent file header.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct FileHeader {
18    pub major_version: u16,
19    pub minor_version: u16,
20    pub flags: u16,
21    pub default_codec_id: u16,
22    pub default_scale_bits: u8,
23    pub default_model_mode: u8,
24    pub declared_block_size: u32,
25}
26
27impl Default for FileHeader {
28    fn default() -> Self {
29        Self {
30            major_version: MAJOR_VERSION,
31            minor_version: MINOR_VERSION,
32            flags: 0,
33            default_codec_id: 2, // BYTE_INTERLEAVED2
34            default_scale_bits: 12,
35            default_model_mode: 0, // per-block
36            declared_block_size: crate::limits::DEFAULT_BLOCK_SIZE,
37        }
38    }
39}
40
41impl FileHeader {
42    /// Serialize to bytes.
43    pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
44        let mut buf = [0u8; HEADER_SIZE];
45        buf[0..8].copy_from_slice(MAGIC);
46        buf[8..10].copy_from_slice(&self.major_version.to_le_bytes());
47        buf[10..12].copy_from_slice(&self.minor_version.to_le_bytes());
48        buf[12..14].copy_from_slice(&(HEADER_SIZE as u16).to_le_bytes());
49        buf[14..16].copy_from_slice(&self.flags.to_le_bytes());
50        buf[16..18].copy_from_slice(&self.default_codec_id.to_le_bytes());
51        buf[18] = self.default_scale_bits;
52        buf[19] = self.default_model_mode;
53        buf[20..24].copy_from_slice(&self.declared_block_size.to_le_bytes());
54        // reserved bytes 24..32 remain zero
55        buf
56    }
57
58    /// Parse from bytes.  Returns `Err` for any invariant violation.
59    pub fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
60        if bytes.len() < HEADER_SIZE {
61            return Err(AppError::Format(FormatError {
62                detail: "header too short".into(),
63                block_index: None,
64                offset: Some(0),
65            }));
66        }
67
68        let magic = &bytes[0..8];
69        if magic != MAGIC {
70            return Err(AppError::Format(FormatError {
71                detail: format!("bad magic: expected RYGRANS\\0, got {:02x?}", magic),
72                block_index: None,
73                offset: Some(0),
74            }));
75        }
76
77        let major_version = u16::from_le_bytes([bytes[8], bytes[9]]);
78        if major_version != MAJOR_VERSION {
79            return Err(AppError::Format(FormatError {
80                detail: format!("unsupported major version: {}", major_version),
81                block_index: None,
82                offset: Some(8),
83            }));
84        }
85
86        let minor_version = u16::from_le_bytes([bytes[10], bytes[11]]);
87        let header_length = u16::from_le_bytes([bytes[12], bytes[13]]);
88        if header_length != HEADER_SIZE as u16 {
89            return Err(AppError::Format(FormatError {
90                detail: format!("unexpected header length: {}", header_length),
91                block_index: None,
92                offset: Some(12),
93            }));
94        }
95
96        let flags = u16::from_le_bytes([bytes[14], bytes[15]]);
97        // Reject unknown required flags
98        let unknown_flags = flags & !flags::KNOWN_FLAGS;
99        if unknown_flags != 0 {
100            return Err(AppError::Format(FormatError {
101                detail: format!("unknown flags: {:04x}", unknown_flags),
102                block_index: None,
103                offset: Some(14),
104            }));
105        }
106
107        // Check reserved bytes (24..32) are zero
108        if bytes[24..32].iter().any(|&b| b != 0) {
109            return Err(AppError::Format(FormatError {
110                detail: "non-zero reserved bytes in header".into(),
111                block_index: None,
112                offset: Some(24),
113            }));
114        }
115
116        let default_codec_id = u16::from_le_bytes([bytes[16], bytes[17]]);
117        let default_scale_bits = bytes[18];
118        let default_model_mode = bytes[19];
119        let declared_block_size = u32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
120
121        if declared_block_size == 0 {
122            return Err(AppError::Format(FormatError {
123                detail: "declared block size is zero".into(),
124                block_index: None,
125                offset: Some(20),
126            }));
127        }
128
129        Ok(Self {
130            major_version,
131            minor_version,
132            flags,
133            default_codec_id,
134            default_scale_bits,
135            default_model_mode,
136            declared_block_size,
137        })
138    }
139
140    /// Whether the GLOBAL_MODEL flag is set.
141    pub fn has_global_model(&self) -> bool {
142        self.flags & flags::GLOBAL_MODEL != 0
143    }
144}