Skip to main content

ox_jsdoc/format/
header.rs

1// @author kazuya kawaguchi (a.k.a. kazupon)
2// @license MIT
3//
4
5//! Header section (40 bytes, fixed).
6//!
7//! See `design/007-binary-ast/format.md#header-40-bytes` for the full layout.
8//!
9//! ```text
10//! byte 0       : version (upper 4-bit major + lower 4-bit minor)
11//! byte 1       : flags   (bit0=compat_mode, bit1-7=reserved)
12//! byte 2-3     : reserved (zero-fill)
13//! byte 4-7     : root_array_offset    (u32)
14//! byte 8-11    : string_offsets_offset(u32)
15//! byte 12-15   : string_data_offset   (u32)
16//! byte 16-19   : extended_data_offset (u32)
17//! byte 20-23   : diagnostics_offset   (u32)
18//! byte 24-27   : nodes_offset         (u32)
19//! byte 28-31   : node_count           (u32)
20//! byte 32-35   : source_text_length   (u32)
21//! byte 36-39   : root_count           (u32)
22//! ```
23//!
24//! All multi-byte integers are little-endian (per the format conventions).
25
26/// Header size in bytes (fixed).
27pub const HEADER_SIZE: usize = 40;
28
29// ---------------------------------------------------------------------------
30// Field byte offsets within the header.
31// ---------------------------------------------------------------------------
32
33/// Offset of the version byte (`bits[7:4]` major, `bits[3:0]` minor).
34pub const VERSION_OFFSET: usize = 0;
35/// Offset of the flag byte.
36pub const FLAGS_OFFSET: usize = 1;
37/// Offset of the 2-byte reserved region (capability flags etc.).
38pub const RESERVED_OFFSET: usize = 2;
39/// Offset of the root index array start (u32).
40pub const ROOT_ARRAY_OFFSET_FIELD: usize = 4;
41/// Offset of the String Offsets section start (u32).
42pub const STRING_OFFSETS_OFFSET_FIELD: usize = 8;
43/// Offset of the String Data section start (u32).
44pub const STRING_DATA_OFFSET_FIELD: usize = 12;
45/// Offset of the Extended Data section start (u32).
46pub const EXTENDED_DATA_OFFSET_FIELD: usize = 16;
47/// Offset of the Diagnostics section start (u32).
48pub const DIAGNOSTICS_OFFSET_FIELD: usize = 20;
49/// Offset of the Nodes section start (u32).
50pub const NODES_OFFSET_FIELD: usize = 24;
51/// Offset of the total node count (u32).
52pub const NODE_COUNT_FIELD: usize = 28;
53/// Offset of the total source text length in UTF-8 bytes (u32).
54pub const SOURCE_TEXT_LENGTH_FIELD: usize = 32;
55/// Offset of the batch root count N (u32).
56pub const ROOT_COUNT_FIELD: usize = 36;
57
58// ---------------------------------------------------------------------------
59// Protocol version (4-bit major + 4-bit minor packed into byte 0).
60// ---------------------------------------------------------------------------
61
62/// Major version supported by this implementation.
63pub const SUPPORTED_MAJOR: u8 = 1;
64/// Minor version supported by this implementation.
65pub const SUPPORTED_MINOR: u8 = 0;
66
67/// Bit shift for extracting the major version from byte 0.
68pub const MAJOR_SHIFT: u8 = 4;
69/// Bit mask for extracting the minor version from byte 0.
70pub const MINOR_MASK: u8 = 0x0F;
71
72/// Pack the (major, minor) tuple into the single version byte.
73#[inline]
74#[must_use]
75pub const fn pack_version(major: u8, minor: u8) -> u8 {
76    (major << MAJOR_SHIFT) | (minor & MINOR_MASK)
77}
78
79/// Extract the major version number from byte 0.
80#[inline]
81#[must_use]
82pub const fn major(version_byte: u8) -> u8 {
83    version_byte >> MAJOR_SHIFT
84}
85
86/// Extract the minor version number from byte 0.
87#[inline]
88#[must_use]
89pub const fn minor(version_byte: u8) -> u8 {
90    version_byte & MINOR_MASK
91}
92
93/// The version byte that this implementation writes (`SUPPORTED_MAJOR.SUPPORTED_MINOR`).
94pub const SUPPORTED_VERSION_BYTE: u8 = pack_version(SUPPORTED_MAJOR, SUPPORTED_MINOR);
95
96// ---------------------------------------------------------------------------
97// Flag bit definitions (byte 1).
98// ---------------------------------------------------------------------------
99
100/// `bit0` of the flag byte: when set, the buffer carries jsdoccomment-compat
101/// extension data on `JsdocBlock` / `JsdocTag` / `JsdocDescriptionLine` /
102/// `JsdocTypeLine` (see `design/007-binary-ast/encoding.md`).
103pub const COMPAT_MODE_BIT: u8 = 0b0000_0001;
104
105/// Mask for the reserved flag bits (bit1..=bit7). Decoders must ignore bits
106/// they do not understand.
107pub const FLAGS_RESERVED_MASK: u8 = 0b1111_1110;
108
109// ---------------------------------------------------------------------------
110// Header struct (in-memory representation, populated by the writer).
111// ---------------------------------------------------------------------------
112
113/// In-memory representation of the binary Header.
114///
115/// This struct is only used while constructing or inspecting a buffer; the
116/// on-wire representation is the 40-byte little-endian byte sequence
117/// described at the top of this module. The struct itself does **not** have a
118/// guaranteed `repr(C)` layout because individual fields are written through
119/// the offset constants rather than via direct struct copy.
120#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
121pub struct Header {
122    /// Packed major+minor version byte.
123    pub version: u8,
124    /// Flag byte (`bit0=compat_mode`, others reserved).
125    pub flags: u8,
126    /// Byte offset of the root index array (Header end if N=0).
127    pub root_array_offset: u32,
128    /// Byte offset of the String Offsets section.
129    pub string_offsets_offset: u32,
130    /// Byte offset of the String Data section.
131    pub string_data_offset: u32,
132    /// Byte offset of the Extended Data section.
133    pub extended_data_offset: u32,
134    /// Byte offset of the Diagnostics section.
135    pub diagnostics_offset: u32,
136    /// Byte offset of the Nodes section.
137    pub nodes_offset: u32,
138    /// Number of node records (including the `node[0]` sentinel).
139    pub node_count: u32,
140    /// Total length of the concatenated source texts in UTF-8 bytes.
141    pub source_text_length: u32,
142    /// Number of roots N in this buffer (1 for the single-comment case).
143    pub root_count: u32,
144}
145
146impl Header {
147    /// Returns whether the `compat_mode` flag bit is set.
148    #[inline]
149    #[must_use]
150    pub const fn compat_mode(&self) -> bool {
151        (self.flags & COMPAT_MODE_BIT) != 0
152    }
153
154    /// Returns the major version stored in `self.version`.
155    #[inline]
156    #[must_use]
157    pub const fn major(&self) -> u8 {
158        major(self.version)
159    }
160
161    /// Returns the minor version stored in `self.version`.
162    #[inline]
163    #[must_use]
164    pub const fn minor(&self) -> u8 {
165        minor(self.version)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn header_size_is_40_bytes() {
175        // The on-wire size is fixed at 40 bytes regardless of struct padding.
176        // This test pins the *layout constant*, not `size_of::<Header>()`.
177        assert_eq!(HEADER_SIZE, 40);
178    }
179
180    #[test]
181    fn header_field_offsets_cover_40_bytes_without_overlap() {
182        // Each field must lie inside the 40-byte header, and the order of
183        // offsets must match the spec.
184        let offsets: &[(usize, usize)] = &[
185            (VERSION_OFFSET, 1),
186            (FLAGS_OFFSET, 1),
187            (RESERVED_OFFSET, 2),
188            (ROOT_ARRAY_OFFSET_FIELD, 4),
189            (STRING_OFFSETS_OFFSET_FIELD, 4),
190            (STRING_DATA_OFFSET_FIELD, 4),
191            (EXTENDED_DATA_OFFSET_FIELD, 4),
192            (DIAGNOSTICS_OFFSET_FIELD, 4),
193            (NODES_OFFSET_FIELD, 4),
194            (NODE_COUNT_FIELD, 4),
195            (SOURCE_TEXT_LENGTH_FIELD, 4),
196            (ROOT_COUNT_FIELD, 4),
197        ];
198
199        let mut cursor = 0usize;
200        for (offset, size) in offsets {
201            assert_eq!(*offset, cursor, "field at expected position");
202            cursor += size;
203        }
204        assert_eq!(cursor, HEADER_SIZE, "fields fully cover the header");
205    }
206
207    #[test]
208    fn version_pack_unpack_roundtrip() {
209        for major in 0u8..=15 {
210            for minor in 0u8..=15 {
211                let byte = pack_version(major, minor);
212                assert_eq!(super::major(byte), major);
213                assert_eq!(super::minor(byte), minor);
214            }
215        }
216    }
217
218    #[test]
219    fn supported_version_byte_is_0x10() {
220        assert_eq!(SUPPORTED_VERSION_BYTE, 0x10);
221        assert_eq!(major(SUPPORTED_VERSION_BYTE), 1);
222        assert_eq!(minor(SUPPORTED_VERSION_BYTE), 0);
223    }
224
225    #[test]
226    fn compat_mode_flag_round_trips() {
227        let mut h = Header::default();
228        assert!(!h.compat_mode());
229        h.flags |= COMPAT_MODE_BIT;
230        assert!(h.compat_mode());
231        // Setting reserved bits must not affect the compat flag query.
232        h.flags |= 0b1010_1010 & FLAGS_RESERVED_MASK;
233        assert!(h.compat_mode());
234    }
235}