Skip to main content

libmagic_rs/parser/
ast.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Abstract Syntax Tree definitions for magic rules
5//!
6//! This module contains the core data structures that represent parsed magic rules
7//! and their components, including offset specifications, type kinds, operators, and values.
8
9use serde::{Deserialize, Serialize};
10use std::num::{NonZeroU32, NonZeroUsize};
11
12/// The width of the length prefix for Pascal strings.
13///
14/// Uppercase suffix letters (`/H`, `/L`) indicate big-endian byte order.
15/// Lowercase suffix letters (`/h`, `/l`) indicate little-endian byte order.
16///
17/// # Examples
18///
19/// ```
20/// use libmagic_rs::parser::ast::PStringLengthWidth;
21/// let width = PStringLengthWidth::OneByte;
22/// assert_eq!(width.byte_count(), 1);
23///
24/// let width = PStringLengthWidth::TwoByteBE;
25/// assert_eq!(width.byte_count(), 2);
26///
27/// let width = PStringLengthWidth::FourByteLE;
28/// assert_eq!(width.byte_count(), 4);
29/// ```
30#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
31#[allow(clippy::enum_variant_names)]
32#[non_exhaustive]
33pub enum PStringLengthWidth {
34    /// 1-byte length prefix (default, `/B` suffix)
35    ///
36    /// # Examples
37    ///
38    /// ```
39    /// use libmagic_rs::parser::ast::PStringLengthWidth;
40    /// let width = PStringLengthWidth::OneByte;
41    /// assert_eq!(width.byte_count(), 1);
42    /// ```
43    OneByte,
44    /// 2-byte big-endian length prefix (`/H` suffix)
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use libmagic_rs::parser::ast::PStringLengthWidth;
50    /// let width = PStringLengthWidth::TwoByteBE;
51    /// assert_eq!(width.byte_count(), 2);
52    /// ```
53    TwoByteBE,
54    /// 2-byte little-endian length prefix (`/h` suffix)
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use libmagic_rs::parser::ast::PStringLengthWidth;
60    /// let width = PStringLengthWidth::TwoByteLE;
61    /// assert_eq!(width.byte_count(), 2);
62    /// ```
63    TwoByteLE,
64    /// 4-byte big-endian length prefix (`/L` suffix)
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use libmagic_rs::parser::ast::PStringLengthWidth;
70    /// let width = PStringLengthWidth::FourByteBE;
71    /// assert_eq!(width.byte_count(), 4);
72    /// ```
73    FourByteBE,
74    /// 4-byte little-endian length prefix (`/l` suffix)
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use libmagic_rs::parser::ast::PStringLengthWidth;
80    /// let width = PStringLengthWidth::FourByteLE;
81    /// assert_eq!(width.byte_count(), 4);
82    /// ```
83    FourByteLE,
84}
85
86impl PStringLengthWidth {
87    /// Returns the number of bytes used for the length prefix.
88    #[must_use]
89    pub fn byte_count(self) -> usize {
90        match self {
91            Self::OneByte => 1,
92            Self::TwoByteBE | Self::TwoByteLE => 2,
93            Self::FourByteBE | Self::FourByteLE => 4,
94        }
95    }
96}
97
98/// Arithmetic operation applied to the value read at an indirect offset's
99/// `base_offset` before the result is used as the final file offset.
100///
101/// magic(5) supports `+`, `-`, `*`, `/`, `%`, `&`, `|`, and `^` between the
102/// pointer-type specifier and the operand inside the parentheses. Addition
103/// and subtraction collapse to [`IndirectAdjustmentOp::Add`] with a signed
104/// `adjustment` (so `(N.X-1)` is `Add(-1)` rather than a separate `Sub`
105/// variant); the remaining operators each have a dedicated variant.
106///
107/// The default is [`IndirectAdjustmentOp::Add`]; an indirect offset with no
108/// arithmetic — just `(base.type)` — is encoded as `Add` with `adjustment:
109/// 0`, preserving backwards compatibility.
110///
111/// # Examples
112///
113/// ```
114/// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
115///
116/// assert_eq!(IndirectAdjustmentOp::default(), IndirectAdjustmentOp::Add);
117/// ```
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
119#[non_exhaustive]
120pub enum IndirectAdjustmentOp {
121    /// Addition (also covers subtraction via negative `adjustment`).
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
127    /// assert_eq!(IndirectAdjustmentOp::default(), IndirectAdjustmentOp::Add);
128    /// ```
129    #[default]
130    Add,
131    /// Multiplication: `pointer_value * adjustment`.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
137    /// let op = IndirectAdjustmentOp::Mul;
138    /// assert_eq!(op, IndirectAdjustmentOp::Mul);
139    /// ```
140    Mul,
141    /// Truncating integer division: `pointer_value / adjustment`. Division
142    /// by zero is rejected by the evaluator with an error.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
148    /// let op = IndirectAdjustmentOp::Div;
149    /// assert_eq!(op, IndirectAdjustmentOp::Div);
150    /// ```
151    Div,
152    /// Remainder: `pointer_value % adjustment`. Modulo by zero is rejected
153    /// by the evaluator with an error.
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
159    /// let op = IndirectAdjustmentOp::Mod;
160    /// assert_eq!(op, IndirectAdjustmentOp::Mod);
161    /// ```
162    Mod,
163    /// Bitwise AND: `pointer_value & adjustment`.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
169    /// let op = IndirectAdjustmentOp::And;
170    /// assert_eq!(op, IndirectAdjustmentOp::And);
171    /// ```
172    And,
173    /// Bitwise OR: `pointer_value | adjustment`.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
179    /// let op = IndirectAdjustmentOp::Or;
180    /// assert_eq!(op, IndirectAdjustmentOp::Or);
181    /// ```
182    Or,
183    /// Bitwise XOR: `pointer_value ^ adjustment`.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use libmagic_rs::parser::ast::IndirectAdjustmentOp;
189    /// let op = IndirectAdjustmentOp::Xor;
190    /// assert_eq!(op, IndirectAdjustmentOp::Xor);
191    /// ```
192    Xor,
193}
194
195/// Offset specification for locating data in files
196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
197#[non_exhaustive]
198pub enum OffsetSpec {
199    /// Absolute offset from file start (or from file end if negative)
200    ///
201    /// Positive values are offsets from the start of the file.
202    /// Negative values are offsets from the end of the file (same as `FromEnd`).
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// use libmagic_rs::parser::ast::OffsetSpec;
208    ///
209    /// let offset = OffsetSpec::Absolute(0x10); // Read at byte 16 from start
210    /// let from_end = OffsetSpec::Absolute(-4); // 4 bytes before end of file
211    /// ```
212    Absolute(i64),
213
214    /// Indirect offset through pointer dereferencing
215    ///
216    /// Reads a pointer value at `base_offset`, interprets it according to `pointer_type`
217    /// and `endian`, then combines `adjustment` with the pointer value using
218    /// `adjustment_op` to get the final offset. The default `adjustment_op`
219    /// is [`IndirectAdjustmentOp::Add`], so `(base.type)` and
220    /// `(base.type+N)` / `(base.type-N)` use addition (subtraction is
221    /// encoded as `Add` with a negative `adjustment`). magic(5) also
222    /// supports multiplicative and bitwise forms inside the parens, e.g.
223    /// `(0x200.s*2)` ([`IndirectAdjustmentOp::Mul`]).
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use libmagic_rs::parser::ast::{OffsetSpec, TypeKind, Endianness, IndirectAdjustmentOp};
229    ///
230    /// let indirect = OffsetSpec::Indirect {
231    ///     base_offset: 0x20,
232    ///     base_relative: false,
233    ///     pointer_type: TypeKind::Long { endian: Endianness::Little, signed: false },
234    ///     adjustment: 4,
235    ///     adjustment_op: IndirectAdjustmentOp::Add,
236    ///     result_relative: false,
237    ///     endian: Endianness::Little,
238    /// };
239    /// ```
240    Indirect {
241        /// Base offset to read pointer from. When `base_relative` is
242        /// `true`, this value is added to the current anchor (last-match
243        /// position) rather than being treated as an absolute file
244        /// position.
245        base_offset: i64,
246        /// If `true`, `base_offset` is relative to the current anchor
247        /// (i.e., `(&N.X)` syntax in magic files). Defaults to `false`
248        /// for backwards compatibility with existing AST snapshots; the
249        /// serde `default` attribute lets older serialized AST round-trip.
250        #[serde(default)]
251        base_relative: bool,
252        /// Type of pointer value
253        pointer_type: TypeKind,
254        /// Operand combined with the pointer value via `adjustment_op`.
255        ///
256        /// For `IndirectAdjustmentOp::Add`, the operand is signed (negative
257        /// values encode subtraction). For multiplicative and bitwise ops
258        /// the operand is interpreted as `i64` but typically magic files
259        /// supply non-negative literals.
260        adjustment: i64,
261        /// Arithmetic operation applied to the pointer value with
262        /// `adjustment` as the operand. Defaults to
263        /// [`IndirectAdjustmentOp::Add`] for legacy AST consumers via
264        /// serde's `default` attribute.
265        #[serde(default)]
266        adjustment_op: IndirectAdjustmentOp,
267        /// If `true`, the resolved offset is added to the current anchor
268        /// instead of being treated as an absolute file position. This
269        /// corresponds to magic-file `&(...)` syntax wrapping an indirect
270        /// spec, e.g., `&(0x10.l)`.
271        #[serde(default)]
272        result_relative: bool,
273        /// Endianness for pointer reading
274        endian: Endianness,
275    },
276
277    /// Relative offset from previous match position
278    ///
279    /// # Examples
280    ///
281    /// ```
282    /// use libmagic_rs::parser::ast::OffsetSpec;
283    ///
284    /// let relative = OffsetSpec::Relative(8); // 8 bytes after previous match
285    /// ```
286    Relative(i64),
287
288    /// Offset from end of file (negative values move towards start)
289    ///
290    /// # Examples
291    ///
292    /// ```
293    /// use libmagic_rs::parser::ast::OffsetSpec;
294    ///
295    /// let from_end = OffsetSpec::FromEnd(-16); // 16 bytes before end of file
296    /// ```
297    FromEnd(i64),
298}
299
300/// Control-flow directive carried by [`TypeKind::Meta`].
301///
302/// These are not value-reading types -- they correspond to magic(5)
303/// control-flow keywords (`default`, `clear`, `name`, `use`, `indirect`,
304/// `offset`) that modify how a rule set is traversed rather than reading
305/// bytes from the buffer. All six variants are fully evaluated by the
306/// engine: `default`/`clear` manage per-level sibling-matched state;
307/// `name`/`use` implement subroutine dispatch; `indirect` re-applies the
308/// root rule database at a resolved offset; and `offset` emits the
309/// current file position as `Value::Uint` for printf-style formatting.
310#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
311#[non_exhaustive]
312pub enum MetaType {
313    /// `default` directive: fires when no sibling at the same indentation
314    /// level has matched at the current offset. See magic(5) for the
315    /// "default" type semantics.
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// use libmagic_rs::parser::ast::MetaType;
321    /// let meta = MetaType::Default;
322    /// assert_eq!(meta, MetaType::Default);
323    /// ```
324    Default,
325    /// `clear` directive: resets the sibling-matched flag so a later
326    /// `default` sibling can fire even if an earlier sibling matched.
327    /// See magic(5) for the "clear" type semantics.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use libmagic_rs::parser::ast::MetaType;
333    /// let meta = MetaType::Clear;
334    /// assert_eq!(meta, MetaType::Clear);
335    /// ```
336    Clear,
337    /// `name <identifier>` directive: declares a named subroutine that
338    /// can be invoked later via [`MetaType::Use`]. See magic(5) for the
339    /// "name" type semantics.
340    ///
341    /// # Examples
342    ///
343    /// ```
344    /// use libmagic_rs::parser::ast::MetaType;
345    /// let meta = MetaType::Name("part2".to_string());
346    /// assert_eq!(meta, MetaType::Name("part2".to_string()));
347    /// ```
348    Name(String),
349    /// `use <identifier>` directive: invokes a named subroutine
350    /// previously declared via [`MetaType::Name`]. See magic(5) for the
351    /// "use" type semantics.
352    ///
353    /// `flip_endian` records the magic(5) `\^` prefix (`use \^name`),
354    /// which flips the endianness of every endian-bearing read inside the
355    /// invoked subroutine (libmagic `softmagic.c` `cvt_flip`; the flip
356    /// toggles and propagates into nested `use` calls). A plain `use name`
357    /// has `flip_endian: false`. See the evaluator's `flip_type_endian`
358    /// and the `SubroutineScope` guard for the runtime semantics.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// use libmagic_rs::parser::ast::MetaType;
364    /// let meta = MetaType::Use { name: "part2".to_string(), flip_endian: false };
365    /// assert_eq!(
366    ///     meta,
367    ///     MetaType::Use { name: "part2".to_string(), flip_endian: false }
368    /// );
369    /// ```
370    Use {
371        /// Identifier of the named subroutine to invoke.
372        name: String,
373        /// Whether to flip endianness of reads inside the subroutine
374        /// (the magic(5) `\^` prefix).
375        flip_endian: bool,
376    },
377    /// `indirect` directive: re-applies the entire magic database at the
378    /// resolved offset. See magic(5) for the "indirect" type semantics.
379    ///
380    /// # Examples
381    ///
382    /// ```
383    /// use libmagic_rs::parser::ast::MetaType;
384    /// let meta = MetaType::Indirect;
385    /// assert_eq!(meta, MetaType::Indirect);
386    /// ```
387    Indirect,
388    /// `offset` type keyword: reports the current file offset rather than
389    /// reading a typed value from the buffer. See magic(5) for the
390    /// "offset" type semantics.
391    ///
392    /// Evaluation: the engine resolves the rule's offset specification
393    /// to an absolute position and emits a `RuleMatch` whose `value` is
394    /// `Value::Uint(position)`. Message templates can reference that
395    /// value through printf-style format specifiers (e.g. `%lld`),
396    /// which are substituted by
397    /// [`crate::output::format::format_magic_message`] at description-
398    /// assembly time. The only supported operator is `x` (`AnyValue`);
399    /// any other operator is `debug!`-logged and skipped.
400    ///
401    /// # Examples
402    ///
403    /// ```
404    /// use libmagic_rs::parser::ast::MetaType;
405    /// let meta = MetaType::Offset;
406    /// assert_eq!(meta, MetaType::Offset);
407    /// ```
408    Offset,
409}
410
411/// Data type specifications for interpreting bytes
412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
413#[non_exhaustive]
414pub enum TypeKind {
415    /// Single byte
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// use libmagic_rs::parser::ast::TypeKind;
421    ///
422    /// let byte = TypeKind::Byte { signed: true };
423    /// assert_eq!(byte, TypeKind::Byte { signed: true });
424    /// ```
425    Byte {
426        /// Whether value is signed
427        signed: bool,
428    },
429    /// 16-bit integer
430    ///
431    /// # Examples
432    ///
433    /// ```
434    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
435    ///
436    /// let short = TypeKind::Short { endian: Endianness::Little, signed: true };
437    /// assert_eq!(short, TypeKind::Short { endian: Endianness::Little, signed: true });
438    /// ```
439    Short {
440        /// Byte order
441        endian: Endianness,
442        /// Whether value is signed
443        signed: bool,
444    },
445    /// 32-bit integer
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
451    ///
452    /// let long = TypeKind::Long { endian: Endianness::Big, signed: false };
453    /// assert_eq!(long, TypeKind::Long { endian: Endianness::Big, signed: false });
454    /// ```
455    Long {
456        /// Byte order
457        endian: Endianness,
458        /// Whether value is signed
459        signed: bool,
460    },
461    /// 32-bit ID3 "synchsafe" integer: four bytes that each contribute
462    /// their low 7 bits. A set high bit is masked, as upstream `cvt_id3` does.
463    ///
464    /// This is libmagic's `FILE_LEID3`/`FILE_BEID3`: magic(5)'s `i`/`I`
465    /// indirect pointer specifiers produce it, and the `leid3`/`beid3` rule
466    /// keywords read it as a value.
467    ///
468    /// # Examples
469    ///
470    /// ```
471    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
472    ///
473    /// let id3 = TypeKind::Id3 { endian: Endianness::Big };
474    /// assert_eq!(id3.bit_width(), Some(32));
475    /// ```
476    Id3 {
477        /// Byte order of the four stored bytes
478        endian: Endianness,
479    },
480    /// 64-bit integer
481    ///
482    /// # Examples
483    ///
484    /// ```
485    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
486    ///
487    /// let quad = TypeKind::Quad { endian: Endianness::Big, signed: true };
488    /// assert_eq!(quad, TypeKind::Quad { endian: Endianness::Big, signed: true });
489    /// ```
490    Quad {
491        /// Byte order
492        endian: Endianness,
493        /// Whether value is signed
494        signed: bool,
495    },
496    /// 32-bit IEEE 754 floating-point
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
502    ///
503    /// let float = TypeKind::Float { endian: Endianness::Big };
504    /// assert_eq!(float, TypeKind::Float { endian: Endianness::Big });
505    /// ```
506    Float {
507        /// Byte order
508        endian: Endianness,
509    },
510    /// 64-bit IEEE 754 double-precision floating-point
511    ///
512    /// # Examples
513    ///
514    /// ```
515    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
516    ///
517    /// let double = TypeKind::Double { endian: Endianness::Big };
518    /// assert_eq!(double, TypeKind::Double { endian: Endianness::Big });
519    /// ```
520    Double {
521        /// Byte order
522        endian: Endianness,
523    },
524    /// 32-bit Unix timestamp (seconds since epoch)
525    ///
526    /// # Examples
527    ///
528    /// ```
529    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
530    ///
531    /// let date = TypeKind::Date { endian: Endianness::Big, utc: true };
532    /// assert_eq!(date, TypeKind::Date { endian: Endianness::Big, utc: true });
533    /// ```
534    Date {
535        /// Byte order
536        endian: Endianness,
537        /// true = UTC, false = local time
538        utc: bool,
539    },
540    /// 64-bit Unix timestamp (seconds since epoch)
541    ///
542    /// # Examples
543    ///
544    /// ```
545    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
546    ///
547    /// let qdate = TypeKind::QDate { endian: Endianness::Little, utc: false };
548    /// assert_eq!(qdate, TypeKind::QDate { endian: Endianness::Little, utc: false });
549    /// ```
550    QDate {
551        /// Byte order
552        endian: Endianness,
553        /// true = UTC, false = local time
554        utc: bool,
555    },
556    /// String data
557    ///
558    /// The `flags` field carries the modifier flags parsed from the
559    /// `/[cCwWtTbf]` suffix on a `string` rule. Default flags (all
560    /// `false`) preserve the existing byte-exact comparison path; any
561    /// non-default flag routes the rule through
562    /// `compare_string_with_flags` in `src/evaluator/types/string.rs`.
563    /// See [`StringFlags`] for per-flag semantics.
564    ///
565    /// # Examples
566    ///
567    /// ```
568    /// use libmagic_rs::parser::ast::{StringFlags, TypeKind};
569    ///
570    /// let s = TypeKind::String { max_length: None, flags: StringFlags::default() };
571    /// assert_eq!(s, TypeKind::String { max_length: None, flags: StringFlags::default() });
572    ///
573    /// let case_insensitive = TypeKind::String {
574    ///     max_length: None,
575    ///     flags: StringFlags::default().with_ignore_lowercase(true),
576    /// };
577    /// assert!(matches!(case_insensitive, TypeKind::String { flags, .. } if flags.ignore_lowercase));
578    /// ```
579    String {
580        /// Maximum length to read
581        max_length: Option<usize>,
582        /// Modifier flags from the `/[cCwWtTbf]` suffix
583        flags: StringFlags,
584    },
585    /// 16-bit-unit string with explicit byte order.
586    ///
587    /// Backs the magic(5) `lestring16` (little-endian) and `bestring16`
588    /// (big-endian) keywords. Each unit occupies two bytes in the file;
589    /// following libmagic, the reader keeps only the low byte of each
590    /// unit (byte 0 for little-endian, byte 1 for big-endian), turns a
591    /// zero low byte under a nonzero high byte into a space, and stops
592    /// at an all-zero unit or the end of the buffer. UCS-2 is never
593    /// decoded (GOTCHAS S6.9).
594    ///
595    /// # Examples
596    ///
597    /// ```
598    /// use libmagic_rs::parser::ast::{TypeKind, Endianness};
599    ///
600    /// let le = TypeKind::String16 { endian: Endianness::Little };
601    /// assert_eq!(le, TypeKind::String16 { endian: Endianness::Little });
602    ///
603    /// let be = TypeKind::String16 { endian: Endianness::Big };
604    /// assert_eq!(be, TypeKind::String16 { endian: Endianness::Big });
605    /// ```
606    String16 {
607        /// Which byte of each 16-bit unit is kept: byte 0 for `Little`
608        /// (and `Native`, which the parser never emits here), byte 1 for
609        /// `Big`.
610        endian: Endianness,
611    },
612    /// Pascal string (length-prefixed, supports 1/2/4-byte prefix, with optional max length)
613    ///
614    /// Pascal strings store the length as a prefix (1, 2, or 4 bytes, with configurable endianness), followed by
615    /// that many bytes of string data. Unlike C strings, they are not null-terminated.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// use libmagic_rs::parser::ast::{TypeKind, PStringLengthWidth};
621    ///
622    /// let pstring = TypeKind::PString { max_length: None, length_width: PStringLengthWidth::OneByte, length_includes_itself: false };
623    /// assert_eq!(pstring, TypeKind::PString { max_length: None, length_width: PStringLengthWidth::OneByte, length_includes_itself: false });
624    ///
625    /// let limited = TypeKind::PString { max_length: Some(64), length_width: PStringLengthWidth::TwoByteBE, length_includes_itself: false };
626    /// assert_eq!(limited, TypeKind::PString { max_length: Some(64), length_width: PStringLengthWidth::TwoByteBE, length_includes_itself: false });
627    ///
628    /// // /J flag: stored length includes the length field itself
629    /// let jpeg = TypeKind::PString { max_length: None, length_width: PStringLengthWidth::TwoByteBE, length_includes_itself: true };
630    /// assert_eq!(jpeg, TypeKind::PString { max_length: None, length_width: PStringLengthWidth::TwoByteBE, length_includes_itself: true });
631    /// ```
632    PString {
633        /// Maximum length to read (caps the length value)
634        max_length: Option<usize>,
635        /// Width of the length prefix
636        length_width: PStringLengthWidth,
637        /// Whether the stored length includes the length field itself (`/J` flag)
638        length_includes_itself: bool,
639    },
640    /// Regular expression matching against file contents
641    ///
642    /// Regex rules match a POSIX-extended regular expression pattern against the
643    /// file buffer. Patterns are compiled with multi-line mode always enabled
644    /// (matching libmagic's unconditional `REG_NEWLINE`), so `^` and `$` match
645    /// at line boundaries and `.` does not match `\n`. The `flags` control
646    /// case sensitivity and anchor advance semantics; the `count` field
647    /// controls the scan window (byte or line bounds). The scan window is
648    /// always capped at 8192 bytes (matching GNU `file`'s `FILE_REGEX_MAX`;
649    /// enforced in the evaluator).
650    ///
651    /// # Examples
652    ///
653    /// ```
654    /// use libmagic_rs::parser::ast::{RegexCount, RegexFlags, TypeKind};
655    /// use std::num::NonZeroU32;
656    ///
657    /// // Plain `regex` -- no flags, default 8192-byte scan window.
658    /// let plain = TypeKind::Regex {
659    ///     flags: RegexFlags::default(),
660    ///     count: RegexCount::Default,
661    /// };
662    ///
663    /// // `regex/1l` -- scan the first line only.
664    /// let first_line = TypeKind::Regex {
665    ///     flags: RegexFlags::default(),
666    ///     count: RegexCount::Lines(NonZeroU32::new(1)),
667    /// };
668    ///
669    /// // `regex/cs` -- case-insensitive, anchor advances to match-start.
670    /// let case_insensitive_start = TypeKind::Regex {
671    ///     flags: RegexFlags::default()
672    ///         .with_case_insensitive(true)
673    ///         .with_start_offset(true),
674    ///     count: RegexCount::Default,
675    /// };
676    /// ```
677    Regex {
678        /// Modifier flags from the `/[cs]` suffix (`/c` case-insensitive,
679        /// `/s` start-offset anchor). Line-mode is encoded by the
680        /// [`RegexCount::Lines`] variant of `count`, not a flag.
681        flags: RegexFlags,
682        /// Scan window specifier: default 8192 bytes, explicit byte
683        /// count, or explicit line count. See [`RegexCount`] for the
684        /// three cases.
685        count: RegexCount,
686    },
687    /// Multi-byte pattern search within a bounded (or open) range
688    ///
689    /// Search rules look for a literal byte pattern starting at the offset.
690    /// Unlike [`TypeKind::String`], which only matches at the exact offset,
691    /// `search` scans forward for the first occurrence. The window is
692    /// controlled by `range`:
693    ///
694    /// * `Some(n)` -- the `search/N` form -- scans at most `n` bytes. `n` is
695    ///   a [`NonZeroUsize`] so `search/0` is unrepresentable.
696    /// * `None` -- bare `search` with no `/N` suffix -- scans from the offset
697    ///   to end-of-buffer. magic(5) documents the count as required, but the
698    ///   reference `file` binary accepts the bare form and treats it as
699    ///   `str_range == 0` (scan-to-EOF), so we follow the implementation.
700    ///
701    /// # Examples
702    ///
703    /// ```
704    /// use libmagic_rs::parser::ast::TypeKind;
705    /// use std::num::NonZeroUsize;
706    ///
707    /// // `search/256` -- scan up to 256 bytes for the literal pattern.
708    /// let bounded = TypeKind::Search {
709    ///     range: NonZeroUsize::new(256),
710    ///     flags: libmagic_rs::parser::ast::SearchFlags::default(),
711    /// };
712    ///
713    /// // bare `search` -- scan the whole remaining buffer.
714    /// let open = TypeKind::Search {
715    ///     range: None,
716    ///     flags: libmagic_rs::parser::ast::SearchFlags::default(),
717    /// };
718    /// ```
719    Search {
720        /// Scan window width in bytes starting at the rule's offset;
721        /// `None` means scan to end-of-buffer (bare `search`).
722        range: Option<NonZeroUsize>,
723        /// Modifier flags from the `/[sCcWwTtBbf]` suffix on a `search`
724        /// rule. The `/s` flag controls anchor advance (match-START vs
725        /// match-END); the eight `StringFlags`-shared letters alter how
726        /// the literal pattern is compared against the file bytes. See
727        /// [`SearchFlags`] for the per-flag semantics.
728        flags: SearchFlags,
729    },
730    /// Control-flow directive (`default`, `clear`, `name`, `use`,
731    /// `indirect`, `offset`).
732    ///
733    /// These magic(5) keywords do not read or compare bytes; they modify
734    /// how a rule set is traversed. All six variants are fully evaluated:
735    /// `default` fires as a fallback when no sibling at the same level
736    /// has matched; `clear` resets that flag; `name`/`use` support
737    /// subroutine definition and invocation; `indirect` re-enters the
738    /// rule set at a resolved offset; `offset` emits the resolved file
739    /// position as `Value::Uint` for printf-style message substitution.
740    /// See [`MetaType`] for the individual variants.
741    ///
742    /// # Examples
743    ///
744    /// ```
745    /// use libmagic_rs::parser::ast::{MetaType, TypeKind};
746    /// let default_rule = TypeKind::Meta(MetaType::Default);
747    /// assert_eq!(default_rule, TypeKind::Meta(MetaType::Default));
748    /// ```
749    Meta(MetaType),
750}
751
752/// Regex modifier flags parsed from the `/[cs]` suffix on a `regex` rule.
753///
754/// The `/l` "line-based window" modifier is **not** represented here; it
755/// lives on [`RegexCount::Lines`] so that the type-level encoding makes
756/// "line count" and "byte count" mutually exclusive. An earlier design
757/// used two separate fields (`line_based: bool` + `count: Option<u32>`)
758/// which admitted the cross-field state `line_based: true, count: None`;
759/// under the current encoding that case is expressed explicitly as
760/// [`RegexCount::Lines(None)`](RegexCount::Lines) -- the `regex/l`
761/// shorthand -- and is behaviorally equivalent to [`RegexCount::Default`]
762/// (both walk the full 8192-byte capped window).
763///
764/// All flags default to `false` via [`RegexFlags::default`], equivalent
765/// to a plain `regex` with no `/c`, `/s`, `/t`, or `/b` suffix.
766///
767/// # Examples
768///
769/// ```
770/// use libmagic_rs::parser::ast::RegexFlags;
771///
772/// let plain = RegexFlags::default();
773/// assert!(!plain.case_insensitive);
774/// assert!(!plain.start_offset);
775/// assert!(!plain.text_test);
776/// assert!(!plain.bin_test);
777///
778/// let case_and_start = RegexFlags::default()
779///     .with_case_insensitive(true)
780///     .with_start_offset(true);
781/// assert!(case_and_start.case_insensitive);
782/// assert!(case_and_start.start_offset);
783/// ```
784#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
785#[allow(clippy::struct_excessive_bools)]
786#[non_exhaustive]
787pub struct RegexFlags {
788    /// `/c` -- case-insensitive matching. When `true`, ASCII letter
789    /// casing is ignored during pattern matching.
790    pub case_insensitive: bool,
791    /// `/s` -- advance the GNU `file` previous-match anchor to the start
792    /// of the matched region instead of its end. Matches libmagic's
793    /// `REGEX_OFFSET_START` flag, which zeros the length contribution in
794    /// `moffset()` for `FILE_REGEX`. Useful for chaining child rules that
795    /// need to re-match from the position where the parent regex began.
796    pub start_offset: bool,
797    /// `/t` -- `STRING_TEXTTEST`. Pass-selection hint: forces the entry
798    /// into the text pass and skips it on a binary buffer when `/b` is
799    /// not also set (GOTCHAS S13.7); no effect on matching.
800    #[serde(default)]
801    pub text_test: bool,
802    /// `/b` -- `STRING_BINTEST`. Pass-selection hint: forces the entry
803    /// into the binary pass and skips it on a text buffer when `/t` is
804    /// not also set (GOTCHAS S13.7); no effect on matching.
805    #[serde(default)]
806    pub bin_test: bool,
807}
808
809impl RegexFlags {
810    /// Builder-style setter for [`RegexFlags::case_insensitive`] (`/c`).
811    ///
812    /// Chain after [`RegexFlags::default()`] to construct `RegexFlags`
813    /// values without exhaustive struct literals. If a new flag is
814    /// added to `RegexFlags` in the future, callers using the builder
815    /// form keep compiling; callers using struct literals would need
816    /// an update.
817    #[must_use]
818    pub const fn with_case_insensitive(mut self, value: bool) -> Self {
819        self.case_insensitive = value;
820        self
821    }
822
823    /// Builder-style setter for [`RegexFlags::start_offset`] (`/s`).
824    ///
825    /// Chain after [`RegexFlags::default()`] to construct `RegexFlags`
826    /// values without exhaustive struct literals.
827    #[must_use]
828    pub const fn with_start_offset(mut self, value: bool) -> Self {
829        self.start_offset = value;
830        self
831    }
832
833    /// Builder-style setter for [`RegexFlags::text_test`] (`/t`).
834    #[must_use]
835    pub const fn with_text_test(mut self, value: bool) -> Self {
836        self.text_test = value;
837        self
838    }
839
840    /// Builder-style setter for [`RegexFlags::bin_test`] (`/b`).
841    #[must_use]
842    pub const fn with_bin_test(mut self, value: bool) -> Self {
843        self.bin_test = value;
844        self
845    }
846}
847
848/// String modifier flags parsed from the `/[cCwWtTbf]` suffix on a `string`
849/// rule.
850///
851/// Mirrors libmagic's `STRING_*` flag bits from `src/file.h`. Each flag
852/// alters how `compare_string_with_flags` walks the pattern and buffer in
853/// parallel. The default (all `false`) preserves byte-exact comparison.
854///
855/// **`/c` vs `/C` are asymmetric**: the pattern character controls
856/// direction. With `/c`, only lowercase pattern chars trigger case-folding
857/// (the file byte is `tolower`'d). With `/C`, only uppercase pattern chars
858/// trigger folding (the file byte is `toupper`'d). Mixed-case patterns
859/// behave intuitively: `/c FoO` matches `FoO`, `Foo`, `FOO` but not
860/// `fOO` (the uppercase `F` is literal). See GOTCHAS S6.5 for the
861/// rationale and `src/softmagic.c` for the canonical libmagic contract.
862///
863/// **`/B` is NOT a string flag** -- it is the `pstring` 1-byte length-width
864/// letter (`PSTRING_1_BE`). `string/B` is rejected at parse time. See
865/// GOTCHAS S6.6.
866///
867/// # Examples
868///
869/// ```
870/// use libmagic_rs::parser::ast::StringFlags;
871///
872/// let plain = StringFlags::default();
873/// assert!(!plain.ignore_lowercase);
874///
875/// let case_insensitive = StringFlags::default().with_ignore_lowercase(true);
876/// assert!(case_insensitive.ignore_lowercase);
877///
878/// let compound = StringFlags::default()
879///     .with_ignore_lowercase(true)
880///     .with_compact_optional_whitespace(true);
881/// assert!(compound.ignore_lowercase);
882/// assert!(compound.compact_optional_whitespace);
883/// ```
884#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
885// libmagic's contract is naturally a bitfield: each flag is a distinct
886// magic(5) letter (/c, /C, /w, /W, /t, /T, /b, /f) with its own STRING_*
887// constant in libmagic src/file.h. Flags compose freely (string/cw is
888// /c plus /w; string/wcCtTbf sets all eight). Folding pairs into enums
889// is possible (whitespace: none|optional|required; case: none|lower|upper)
890// but would obscure the libmagic mapping and produce verbose match arms
891// in every consumer. The bool-per-flag layout mirrors `RegexFlags` and
892// the libmagic source -- the clippy lint is overruled by the design.
893#[allow(clippy::struct_excessive_bools)]
894#[non_exhaustive]
895pub struct StringFlags {
896    /// `/W` -- `STRING_COMPACT_WHITESPACE`. Pattern whitespace requires at
897    /// least one whitespace byte in the file, then any further whitespace
898    /// in the file is consumed greedily.
899    pub compact_whitespace: bool,
900    /// `/w` -- `STRING_COMPACT_OPTIONAL_WHITESPACE`. Pattern whitespace
901    /// matches zero or more whitespace bytes in the file.
902    pub compact_optional_whitespace: bool,
903    /// `/c` -- `STRING_IGNORE_LOWERCASE`. When the pattern char is
904    /// lowercase, the file byte is `to_ascii_lowercase`'d before
905    /// comparison. Uppercase pattern chars are compared literally.
906    pub ignore_lowercase: bool,
907    /// `/C` -- `STRING_IGNORE_UPPERCASE`. When the pattern char is
908    /// uppercase, the file byte is `to_ascii_uppercase`'d before
909    /// comparison. Lowercase pattern chars are compared literally.
910    pub ignore_uppercase: bool,
911    /// `/t` -- `STRING_TEXTTEST`. Pass-selection hint: makes the entry a
912    /// text-pass entry and skips it on a binary buffer when `/b` is not
913    /// also set (GOTCHAS S13.7); no comparison effect.
914    pub text_test: bool,
915    /// `/T` -- `STRING_TRIM`. Trim leading and trailing ASCII whitespace
916    /// from the pattern before comparison. The trim is applied at
917    /// evaluation time (in `read_pattern_match`) so the AST keeps the
918    /// original pattern bytes; the comparison function receives the
919    /// trimmed slice.
920    pub trim: bool,
921    /// `/b` -- `STRING_BINTEST`. Pass-selection hint: skipped on a text
922    /// buffer when `/t` is not also set. It never changes a `string`
923    /// entry's pass, which `/t` alone decides, so `string/bt` is
924    /// text-pass-only (GOTCHAS S13.7); no comparison effect.
925    pub bin_test: bool,
926    /// `/f` -- `STRING_FULL_WORD`. Post-match check that the byte after
927    /// the matched region is either end-of-buffer or a non-word
928    /// character (ASCII alphanumeric or `_`).
929    pub full_word: bool,
930}
931
932impl StringFlags {
933    /// Returns `true` when every flag is `false` (the byte-exact fast
934    /// path). The evaluator dispatcher uses this to skip the
935    /// parallel-walk comparison when no flags are set.
936    #[must_use]
937    pub const fn is_empty(self) -> bool {
938        !self.compact_whitespace
939            && !self.compact_optional_whitespace
940            && !self.ignore_lowercase
941            && !self.ignore_uppercase
942            && !self.text_test
943            && !self.trim
944            && !self.bin_test
945            && !self.full_word
946    }
947
948    /// Builder-style setter for `compact_whitespace` (`/W`).
949    #[must_use]
950    pub const fn with_compact_whitespace(mut self, value: bool) -> Self {
951        self.compact_whitespace = value;
952        self
953    }
954
955    /// Builder-style setter for `compact_optional_whitespace` (`/w`).
956    #[must_use]
957    pub const fn with_compact_optional_whitespace(mut self, value: bool) -> Self {
958        self.compact_optional_whitespace = value;
959        self
960    }
961
962    /// Builder-style setter for `ignore_lowercase` (`/c`).
963    #[must_use]
964    pub const fn with_ignore_lowercase(mut self, value: bool) -> Self {
965        self.ignore_lowercase = value;
966        self
967    }
968
969    /// Builder-style setter for `ignore_uppercase` (`/C`).
970    #[must_use]
971    pub const fn with_ignore_uppercase(mut self, value: bool) -> Self {
972        self.ignore_uppercase = value;
973        self
974    }
975
976    /// Builder-style setter for `text_test` (`/t`).
977    #[must_use]
978    pub const fn with_text_test(mut self, value: bool) -> Self {
979        self.text_test = value;
980        self
981    }
982
983    /// Builder-style setter for `trim` (`/T`).
984    #[must_use]
985    pub const fn with_trim(mut self, value: bool) -> Self {
986        self.trim = value;
987        self
988    }
989
990    /// Builder-style setter for `bin_test` (`/b`).
991    #[must_use]
992    pub const fn with_bin_test(mut self, value: bool) -> Self {
993        self.bin_test = value;
994        self
995    }
996
997    /// Builder-style setter for `full_word` (`/f`).
998    #[must_use]
999    pub const fn with_full_word(mut self, value: bool) -> Self {
1000        self.full_word = value;
1001        self
1002    }
1003}
1004
1005/// Search modifier flags parsed from the `/[sCcWwTtBbf]` suffix on a
1006/// `search` rule.
1007///
1008/// Mirrors [`StringFlags`] for the eight `STRING_*` letters that alter
1009/// the literal-pattern comparison (`/c`, `/C`, `/w`, `/W`, `/t`, `/T`,
1010/// `/b`, `/f`), plus a search-only `start_anchor` field for `/s` which
1011/// shifts the GNU `file` previous-match anchor to the START of the
1012/// matched region. The default (all `false`) preserves byte-exact
1013/// comparison and match-END anchor advance.
1014///
1015/// `SearchFlags` is structurally parallel to `StringFlags`: when one
1016/// struct grows a field, the other gains the same field in lockstep
1017/// so that [`SearchFlags::to_string_flags`] can keep handing off to
1018/// `compare_string_with_flags` without a generic refactor. The
1019/// search-only `start_anchor` field has no analog in `string` rules.
1020///
1021/// **`/c` vs `/C` are asymmetric** in the same way as [`StringFlags`]:
1022/// the pattern character controls fold direction. See [`StringFlags`]
1023/// and GOTCHAS S6.5 for the rationale.
1024///
1025/// # Examples
1026///
1027/// ```
1028/// use libmagic_rs::parser::ast::SearchFlags;
1029///
1030/// let plain = SearchFlags::default();
1031/// assert!(!plain.start_anchor);
1032/// assert!(plain.is_empty());
1033/// assert!(!plain.needs_byte_compare());
1034///
1035/// let start = SearchFlags::default().with_start_anchor(true);
1036/// assert!(start.start_anchor);
1037/// assert!(!start.is_empty());
1038/// // /s is anchor-only -- does not force the byte-compare slow path.
1039/// assert!(!start.needs_byte_compare());
1040///
1041/// let case = SearchFlags::default().with_ignore_lowercase(true);
1042/// assert!(case.needs_byte_compare());
1043/// ```
1044#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1045// libmagic's contract is naturally a bitfield: each flag is a distinct
1046// magic(5) letter with its own STRING_*/SEARCH_* constant in libmagic
1047// src/file.h. Flags compose freely (search/cs is /c plus /s; search/sWcT
1048// sets four). Folding pairs into enums is possible but would obscure
1049// the libmagic mapping and produce verbose match arms in every consumer.
1050// The bool-per-flag layout mirrors `StringFlags` and `RegexFlags` and the
1051// libmagic source -- the clippy lint is overruled by the design.
1052#[allow(clippy::struct_excessive_bools)]
1053#[non_exhaustive]
1054pub struct SearchFlags {
1055    /// `/W` -- `STRING_COMPACT_WHITESPACE`. Pattern whitespace requires at
1056    /// least one whitespace byte in the file, then any further whitespace
1057    /// in the file is consumed greedily.
1058    pub compact_whitespace: bool,
1059    /// `/w` -- `STRING_COMPACT_OPTIONAL_WHITESPACE`. Pattern whitespace
1060    /// matches zero or more whitespace bytes in the file.
1061    pub compact_optional_whitespace: bool,
1062    /// `/c` -- `STRING_IGNORE_LOWERCASE`. When the pattern char is
1063    /// lowercase, the file byte is `to_ascii_lowercase`'d before
1064    /// comparison. Uppercase pattern chars are compared literally.
1065    pub ignore_lowercase: bool,
1066    /// `/C` -- `STRING_IGNORE_UPPERCASE`. When the pattern char is
1067    /// uppercase, the file byte is `to_ascii_uppercase`'d before
1068    /// comparison. Lowercase pattern chars are compared literally.
1069    pub ignore_uppercase: bool,
1070    /// `/t` -- `STRING_TEXTTEST`. Pass-selection hint: makes the entry a
1071    /// text-pass entry and skips it on a binary buffer when `/b` is not
1072    /// also set (GOTCHAS S13.7); no comparison effect.
1073    pub text_test: bool,
1074    /// `/T` -- `STRING_TRIM`. Trim leading and trailing ASCII whitespace
1075    /// from the pattern before comparison.
1076    pub trim: bool,
1077    /// `/b` -- `STRING_BINTEST`. Pass-selection hint: makes the entry a
1078    /// binary-pass entry and skips it on a text buffer when `/t` is not
1079    /// also set; `search/bt` runs in both passes (GOTCHAS S13.7); no
1080    /// comparison effect.
1081    pub bin_test: bool,
1082    /// `/f` -- `STRING_FULL_WORD`. Post-match check that the byte after
1083    /// the matched region is either end-of-buffer or a non-word
1084    /// character (ASCII alphanumeric or `_`).
1085    pub full_word: bool,
1086    /// `/s` -- magic(5) "search-start" flag. When `true`, the GNU `file`
1087    /// previous-match anchor advance lands on the match-START index
1088    /// rather than match-END (the default). Mirrors libmagic's
1089    /// `FILE_SEARCH` anchor handling in `src/softmagic.c::moffset`. The
1090    /// dispatch happens in
1091    /// `src/evaluator/types/search.rs::search_bytes_consumed`.
1092    pub start_anchor: bool,
1093}
1094
1095impl SearchFlags {
1096    /// Returns `true` when every flag is `false` (default-constructed).
1097    #[must_use]
1098    pub const fn is_empty(self) -> bool {
1099        !self.compact_whitespace
1100            && !self.compact_optional_whitespace
1101            && !self.ignore_lowercase
1102            && !self.ignore_uppercase
1103            && !self.text_test
1104            && !self.trim
1105            && !self.bin_test
1106            && !self.full_word
1107            && !self.start_anchor
1108    }
1109
1110    /// Returns `true` when any flag alters the literal-pattern
1111    /// comparison, forcing the byte-walk slow path through
1112    /// `compare_string_with_flags`. The anchor-only / metadata-only
1113    /// flags (`/s`, `/t`, `/b`) do **not** trigger byte-compare;
1114    /// they preserve the `memchr::memmem::find` fast path.
1115    #[must_use]
1116    pub const fn needs_byte_compare(self) -> bool {
1117        self.compact_whitespace
1118            || self.compact_optional_whitespace
1119            || self.ignore_lowercase
1120            || self.ignore_uppercase
1121            || self.trim
1122            || self.full_word
1123    }
1124
1125    /// Project the eight shared flag fields onto a [`StringFlags`] for
1126    /// handoff to `compare_string_with_flags`. The search-only
1127    /// `start_anchor` field is dropped (it is anchor-advance policy,
1128    /// not comparison policy).
1129    ///
1130    /// # Examples
1131    ///
1132    /// ```
1133    /// use libmagic_rs::parser::ast::SearchFlags;
1134    ///
1135    /// let sf = SearchFlags::default()
1136    ///     .with_ignore_lowercase(true)
1137    ///     .with_trim(true)
1138    ///     .with_start_anchor(true);
1139    /// let projected = sf.to_string_flags();
1140    /// assert!(projected.ignore_lowercase);
1141    /// assert!(projected.trim);
1142    /// // /s has no analog in StringFlags.
1143    /// ```
1144    #[must_use]
1145    pub const fn to_string_flags(self) -> StringFlags {
1146        StringFlags {
1147            compact_whitespace: self.compact_whitespace,
1148            compact_optional_whitespace: self.compact_optional_whitespace,
1149            ignore_lowercase: self.ignore_lowercase,
1150            ignore_uppercase: self.ignore_uppercase,
1151            text_test: self.text_test,
1152            trim: self.trim,
1153            bin_test: self.bin_test,
1154            full_word: self.full_word,
1155        }
1156    }
1157
1158    /// Builder-style setter for `compact_whitespace` (`/W`).
1159    #[must_use]
1160    pub const fn with_compact_whitespace(mut self, value: bool) -> Self {
1161        self.compact_whitespace = value;
1162        self
1163    }
1164
1165    /// Builder-style setter for `compact_optional_whitespace` (`/w`).
1166    #[must_use]
1167    pub const fn with_compact_optional_whitespace(mut self, value: bool) -> Self {
1168        self.compact_optional_whitespace = value;
1169        self
1170    }
1171
1172    /// Builder-style setter for `ignore_lowercase` (`/c`).
1173    #[must_use]
1174    pub const fn with_ignore_lowercase(mut self, value: bool) -> Self {
1175        self.ignore_lowercase = value;
1176        self
1177    }
1178
1179    /// Builder-style setter for `ignore_uppercase` (`/C`).
1180    #[must_use]
1181    pub const fn with_ignore_uppercase(mut self, value: bool) -> Self {
1182        self.ignore_uppercase = value;
1183        self
1184    }
1185
1186    /// Builder-style setter for `text_test` (`/t`).
1187    #[must_use]
1188    pub const fn with_text_test(mut self, value: bool) -> Self {
1189        self.text_test = value;
1190        self
1191    }
1192
1193    /// Builder-style setter for `trim` (`/T`).
1194    #[must_use]
1195    pub const fn with_trim(mut self, value: bool) -> Self {
1196        self.trim = value;
1197        self
1198    }
1199
1200    /// Builder-style setter for `bin_test` (`/b`).
1201    #[must_use]
1202    pub const fn with_bin_test(mut self, value: bool) -> Self {
1203        self.bin_test = value;
1204        self
1205    }
1206
1207    /// Builder-style setter for `full_word` (`/f`).
1208    #[must_use]
1209    pub const fn with_full_word(mut self, value: bool) -> Self {
1210        self.full_word = value;
1211        self
1212    }
1213
1214    /// Builder-style setter for `start_anchor` (`/s`).
1215    #[must_use]
1216    pub const fn with_start_anchor(mut self, value: bool) -> Self {
1217        self.start_anchor = value;
1218        self
1219    }
1220}
1221
1222/// Scan window specifier for a [`TypeKind::Regex`] rule.
1223///
1224/// Encodes the three mutually-exclusive scan modes in a single enum so
1225/// that the "byte count" and "line count" cases cannot be confused. The
1226/// `regex/l` shorthand (line mode with no explicit count) is represented
1227/// explicitly as [`RegexCount::Lines(None)`](RegexCount::Lines), which
1228/// is behaviorally equivalent to [`RegexCount::Default`] -- both walk
1229/// the full 8192-byte capped window -- but preserves the magic-file
1230/// surface syntax of the original rule. The 8192-byte hard cap
1231/// (matching GNU `file`'s `FILE_REGEX_MAX`) is applied by the evaluator
1232/// on every variant.
1233///
1234/// # Examples
1235///
1236/// ```
1237/// use libmagic_rs::parser::ast::RegexCount;
1238/// use std::num::NonZeroU32;
1239///
1240/// // Plain `regex` (no suffix): default 8192-byte window.
1241/// assert_eq!(RegexCount::default(), RegexCount::Default);
1242///
1243/// // `regex/100`: scan at most 100 bytes.
1244/// let hundred_bytes = RegexCount::Bytes(NonZeroU32::new(100).unwrap());
1245///
1246/// // `regex/1l`: scan the first line.
1247/// let one_line = RegexCount::Lines(NonZeroU32::new(1));
1248///
1249/// // `regex/l`: line-mode with no explicit count (walks terminators
1250/// // to the end of the 8192-byte capped window).
1251/// let unbounded_lines = RegexCount::Lines(None);
1252/// ```
1253#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1254pub enum RegexCount {
1255    /// No scan bound (plain `regex` with no suffix). Scans the default
1256    /// 8192-byte window from the rule's offset.
1257    #[default]
1258    Default,
1259    /// Byte-bounded scan (`regex/N` with no `/l` flag). The window is
1260    /// `min(n, 8192, remaining_buffer)` bytes long. `NonZeroU32` makes
1261    /// a zero-byte scan unrepresentable.
1262    Bytes(NonZeroU32),
1263    /// Line-bounded scan (`regex/Nl` or `regex/l`). The window walks
1264    /// LF / CRLF / bare CR line terminators from the offset. With
1265    /// `Some(n)`, the walk stops after the Nth terminator (inclusive).
1266    /// With `None` (the `regex/l` shorthand), the walk continues to
1267    /// the end of the 8192-byte capped window. Either way the
1268    /// effective byte window is capped at 8192.
1269    Lines(Option<NonZeroU32>),
1270}
1271
1272impl TypeKind {
1273    /// Returns the bit width of integer types, or `None` for non-integer types (e.g., String).
1274    ///
1275    /// # Examples
1276    ///
1277    /// ```
1278    /// use libmagic_rs::parser::ast::{Endianness, StringFlags, TypeKind};
1279    ///
1280    /// assert_eq!(TypeKind::Byte { signed: false }.bit_width(), Some(8));
1281    /// assert_eq!(TypeKind::Short { endian: Endianness::Native, signed: true }.bit_width(), Some(16));
1282    /// assert_eq!(TypeKind::Long { endian: Endianness::Native, signed: true }.bit_width(), Some(32));
1283    /// assert_eq!(TypeKind::Quad { endian: Endianness::Native, signed: true }.bit_width(), Some(64));
1284    /// assert_eq!(TypeKind::Float { endian: Endianness::Native }.bit_width(), Some(32));
1285    /// assert_eq!(TypeKind::Double { endian: Endianness::Native }.bit_width(), Some(64));
1286    /// assert_eq!(TypeKind::String { max_length: None, flags: StringFlags::default() }.bit_width(), None);
1287    /// ```
1288    #[must_use]
1289    pub const fn bit_width(&self) -> Option<u32> {
1290        match self {
1291            Self::Byte { .. } => Some(8),
1292            Self::Short { .. } => Some(16),
1293            Self::Long { .. } | Self::Id3 { .. } | Self::Float { .. } | Self::Date { .. } => {
1294                Some(32)
1295            }
1296            Self::Quad { .. } | Self::Double { .. } | Self::QDate { .. } => Some(64),
1297            Self::String { .. }
1298            | Self::String16 { .. }
1299            | Self::PString { .. }
1300            | Self::Regex { .. }
1301            | Self::Search { .. }
1302            | Self::Meta(_) => None,
1303        }
1304    }
1305}
1306
1307/// Comparison and bitwise operators
1308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1309#[non_exhaustive]
1310pub enum Operator {
1311    /// Equality comparison (`=` or `==`)
1312    ///
1313    /// # Examples
1314    ///
1315    /// ```
1316    /// use libmagic_rs::parser::ast::Operator;
1317    ///
1318    /// let op = Operator::Equal;
1319    /// assert_eq!(op, Operator::Equal);
1320    /// ```
1321    Equal,
1322    /// Inequality comparison (`!=` or `<>`)
1323    ///
1324    /// # Examples
1325    ///
1326    /// ```
1327    /// use libmagic_rs::parser::ast::Operator;
1328    ///
1329    /// let op = Operator::NotEqual;
1330    /// assert_eq!(op, Operator::NotEqual);
1331    /// ```
1332    NotEqual,
1333    /// Less-than comparison (`<`)
1334    ///
1335    /// # Examples
1336    ///
1337    /// ```
1338    /// use libmagic_rs::parser::ast::Operator;
1339    ///
1340    /// let op = Operator::LessThan;
1341    /// assert_eq!(op, Operator::LessThan);
1342    /// ```
1343    LessThan,
1344    /// Greater-than comparison (`>`)
1345    ///
1346    /// # Examples
1347    ///
1348    /// ```
1349    /// use libmagic_rs::parser::ast::Operator;
1350    ///
1351    /// let op = Operator::GreaterThan;
1352    /// assert_eq!(op, Operator::GreaterThan);
1353    /// ```
1354    GreaterThan,
1355    /// Less-than-or-equal comparison (`<=`)
1356    ///
1357    /// # Examples
1358    ///
1359    /// ```
1360    /// use libmagic_rs::parser::ast::Operator;
1361    ///
1362    /// let op = Operator::LessEqual;
1363    /// assert_eq!(op, Operator::LessEqual);
1364    /// ```
1365    LessEqual,
1366    /// Greater-than-or-equal comparison (`>=`)
1367    ///
1368    /// # Examples
1369    ///
1370    /// ```
1371    /// use libmagic_rs::parser::ast::Operator;
1372    ///
1373    /// let op = Operator::GreaterEqual;
1374    /// assert_eq!(op, Operator::GreaterEqual);
1375    /// ```
1376    GreaterEqual,
1377    /// Bitwise AND operation without mask (`&`)
1378    ///
1379    /// # Examples
1380    ///
1381    /// ```
1382    /// use libmagic_rs::parser::ast::Operator;
1383    ///
1384    /// let op = Operator::BitwiseAnd;
1385    /// assert_eq!(op, Operator::BitwiseAnd);
1386    /// ```
1387    BitwiseAnd,
1388    /// Bitwise AND operation with mask value (`&` with a mask operand)
1389    ///
1390    /// # Examples
1391    ///
1392    /// ```
1393    /// use libmagic_rs::parser::ast::Operator;
1394    ///
1395    /// let op = Operator::BitwiseAndMask(0xFF00);
1396    /// assert_eq!(op, Operator::BitwiseAndMask(0xFF00));
1397    /// ```
1398    BitwiseAndMask(u64),
1399    /// Bitwise XOR operation (`^`)
1400    ///
1401    /// # Examples
1402    ///
1403    /// ```
1404    /// use libmagic_rs::parser::ast::Operator;
1405    ///
1406    /// let op = Operator::BitwiseXor;
1407    /// assert_eq!(op, Operator::BitwiseXor);
1408    /// ```
1409    BitwiseXor,
1410    /// Bitwise NOT/complement operation (`~`)
1411    ///
1412    /// # Examples
1413    ///
1414    /// ```
1415    /// use libmagic_rs::parser::ast::Operator;
1416    ///
1417    /// let op = Operator::BitwiseNot;
1418    /// assert_eq!(op, Operator::BitwiseNot);
1419    /// ```
1420    BitwiseNot,
1421    /// Match any value; condition always succeeds (`x`)
1422    ///
1423    /// # Examples
1424    ///
1425    /// ```
1426    /// use libmagic_rs::parser::ast::Operator;
1427    ///
1428    /// let op = Operator::AnyValue;
1429    /// assert_eq!(op, Operator::AnyValue);
1430    /// ```
1431    AnyValue,
1432}
1433
1434/// Value types for rule matching
1435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1436#[non_exhaustive]
1437pub enum Value {
1438    /// Unsigned integer value
1439    ///
1440    /// # Examples
1441    ///
1442    /// ```
1443    /// use libmagic_rs::parser::ast::Value;
1444    ///
1445    /// let val = Value::Uint(0xDEAD_BEEF);
1446    /// assert_eq!(val, Value::Uint(0xDEAD_BEEF));
1447    /// ```
1448    Uint(u64),
1449    /// Signed integer value
1450    ///
1451    /// # Examples
1452    ///
1453    /// ```
1454    /// use libmagic_rs::parser::ast::Value;
1455    ///
1456    /// let val = Value::Int(-42);
1457    /// assert_eq!(val, Value::Int(-42));
1458    /// ```
1459    Int(i64),
1460    /// Floating-point value (used for `float` and `double` types)
1461    ///
1462    /// # Examples
1463    ///
1464    /// ```
1465    /// use libmagic_rs::parser::ast::Value;
1466    ///
1467    /// let val = Value::Float(3.14);
1468    /// assert_eq!(val, Value::Float(3.14));
1469    /// ```
1470    Float(f64),
1471    /// Byte sequence
1472    ///
1473    /// # Examples
1474    ///
1475    /// ```
1476    /// use libmagic_rs::parser::ast::Value;
1477    ///
1478    /// let val = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
1479    /// assert_eq!(val, Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]));
1480    /// ```
1481    Bytes(Vec<u8>),
1482    /// String value
1483    ///
1484    /// # Examples
1485    ///
1486    /// ```
1487    /// use libmagic_rs::parser::ast::Value;
1488    ///
1489    /// let val = Value::String("MZ".to_string());
1490    /// assert_eq!(val, Value::String("MZ".to_string()));
1491    /// ```
1492    String(String),
1493}
1494
1495/// Endianness specification for multi-byte values
1496#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1497pub enum Endianness {
1498    /// Little-endian byte order (least significant byte first)
1499    ///
1500    /// # Examples
1501    ///
1502    /// ```
1503    /// use libmagic_rs::parser::ast::Endianness;
1504    ///
1505    /// let e = Endianness::Little;
1506    /// assert_eq!(e, Endianness::Little);
1507    /// ```
1508    Little,
1509    /// Big-endian byte order (most significant byte first)
1510    ///
1511    /// # Examples
1512    ///
1513    /// ```
1514    /// use libmagic_rs::parser::ast::Endianness;
1515    ///
1516    /// let e = Endianness::Big;
1517    /// assert_eq!(e, Endianness::Big);
1518    /// ```
1519    Big,
1520    /// Native system byte order (matches target architecture)
1521    ///
1522    /// # Examples
1523    ///
1524    /// ```
1525    /// use libmagic_rs::parser::ast::Endianness;
1526    ///
1527    /// let e = Endianness::Native;
1528    /// assert_eq!(e, Endianness::Native);
1529    /// ```
1530    Native,
1531}
1532
1533/// Strength modifier for magic rules
1534///
1535/// Strength modifiers adjust the default strength calculation for a rule.
1536/// They are specified using the `!:strength` directive in magic files.
1537///
1538/// # Examples
1539///
1540/// ```
1541/// use libmagic_rs::parser::ast::StrengthModifier;
1542///
1543/// let add = StrengthModifier::Add(10);      // !:strength +10
1544/// let sub = StrengthModifier::Subtract(5);  // !:strength -5
1545/// let mul = StrengthModifier::Multiply(2);  // !:strength *2
1546/// let div = StrengthModifier::Divide(2);    // !:strength /2
1547/// let set = StrengthModifier::Set(50);      // !:strength =50
1548/// ```
1549#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1550pub enum StrengthModifier {
1551    /// Add to the default strength: `!:strength +N`
1552    Add(i32),
1553    /// Subtract from the default strength: `!:strength -N`
1554    Subtract(i32),
1555    /// Multiply the default strength: `!:strength *N`
1556    Multiply(i32),
1557    /// Divide the default strength: `!:strength /N`
1558    Divide(i32),
1559    /// Set strength to an absolute value: `!:strength =N` or `!:strength N`
1560    Set(i32),
1561}
1562
1563/// Arithmetic operation applied to a value read from the file *before* the
1564/// rule's comparison operator is evaluated.
1565///
1566/// magic(5) supports `+`, `-`, `*`, `/`, `%`, `|`, and `^` between the type
1567/// keyword and the comparison value (e.g., `lelong+1 x volume %d` reads a
1568/// long, adds 1, and formats the transformed value into the message).
1569/// Bitwise AND (`&MASK`) is *not* part of this enum because it is already
1570/// represented at the operator level via [`Operator::BitwiseAndMask`].
1571///
1572/// The operand is signed (`i64`) so that subtraction and negative multipliers
1573/// round-trip cleanly. Bitwise ops reinterpret the operand as a `u64` bit
1574/// pattern at evaluation time, matching libmagic's `apprentice.c::mconvert`.
1575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1576#[non_exhaustive]
1577pub enum ValueTransformOp {
1578    /// Addition (`type+N`).
1579    Add,
1580    /// Subtraction (`type-N`).
1581    Sub,
1582    /// Multiplication (`type*N`).
1583    Mul,
1584    /// Truncating integer division (`type/N`). Division by zero is rejected
1585    /// at evaluation time.
1586    Div,
1587    /// Remainder (`type%N`). Modulo by zero is rejected at evaluation time.
1588    Mod,
1589    /// Bitwise AND (`type&N`).
1590    ///
1591    /// magic(5) `&MASK` was historically encoded at the operator level
1592    /// via [`Operator::BitwiseAndMask`] (which combines mask+equal in
1593    /// one step). That encoding cannot represent rules like `lelong&0xff
1594    /// x %d` (mask + any-value, with the masked value used in format
1595    /// substitution). The parser promotes `&MASK` to this `BitAnd`
1596    /// transform when followed by another operator (`x`, `>`, `!=`, ...)
1597    /// so the read value is masked before comparison and before printf
1598    /// substitution. The legacy `&MASK VALUE` form (mask + implicit
1599    /// equal) keeps using `Operator::BitwiseAndMask` for backwards
1600    /// compatibility.
1601    BitAnd,
1602    /// Bitwise OR (`type|N`).
1603    Or,
1604    /// Bitwise XOR (`type^N`).
1605    Xor,
1606}
1607
1608/// A pre-comparison value transform: `(op, operand)`.
1609///
1610/// Applied to the value read from the file before the rule's comparison
1611/// operator runs. See [`ValueTransformOp`] for the supported operations.
1612///
1613/// # Examples
1614///
1615/// ```
1616/// use libmagic_rs::parser::ast::{ValueTransform, ValueTransformOp};
1617///
1618/// // `lelong+1` -> add 1 to the read value
1619/// let t = ValueTransform::new(ValueTransformOp::Add, 1);
1620/// assert_eq!(t.op, ValueTransformOp::Add);
1621/// assert_eq!(t.operand, 1);
1622/// ```
1623#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1624#[non_exhaustive]
1625pub struct ValueTransform {
1626    /// Operation to apply.
1627    pub op: ValueTransformOp,
1628    /// Operand to combine with the read value.
1629    pub operand: i64,
1630}
1631
1632impl ValueTransform {
1633    /// Construct a new `ValueTransform` from an op and an operand.
1634    #[must_use]
1635    pub const fn new(op: ValueTransformOp, operand: i64) -> Self {
1636        Self { op, operand }
1637    }
1638}
1639
1640/// Magic rule representation in the AST
1641#[derive(Debug, Clone, Serialize, Deserialize)]
1642#[non_exhaustive]
1643pub struct MagicRule {
1644    /// Offset specification for where to read data
1645    pub offset: OffsetSpec,
1646    /// Type of data to read and interpret
1647    pub typ: TypeKind,
1648    /// Comparison operator to apply
1649    pub op: Operator,
1650    /// Expected value for comparison
1651    pub value: Value,
1652    /// Human-readable message for this rule
1653    pub message: String,
1654    /// Child rules that are evaluated if this rule matches
1655    pub children: Vec<MagicRule>,
1656    /// Indentation level for hierarchical rules
1657    pub level: u32,
1658    /// Optional strength modifier from `!:strength` directive
1659    pub strength_modifier: Option<StrengthModifier>,
1660    /// Optional pre-comparison value transform from a magic-file
1661    /// type-suffix like `lelong+1` or `ulequad/1073741824`. When set,
1662    /// the read value is transformed *before* `op` is evaluated and
1663    /// before the message's `%`-format substitution, so format
1664    /// specifiers see the post-transform number.
1665    ///
1666    /// `#[serde(default)]` keeps existing serialized AST snapshots
1667    /// (which never had this field) round-tripping correctly: missing
1668    /// fields deserialize to `None`, which means "no transform" --
1669    /// the historical behavior.
1670    #[serde(default)]
1671    pub value_transform: Option<ValueTransform>,
1672}
1673
1674/// Validation errors returned by [`MagicRule::validate`].
1675#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1676#[non_exhaustive]
1677pub enum MagicRuleValidationError {
1678    /// Rule message is empty. Messages are user-facing and required
1679    /// for meaningful output.
1680    #[error("rule message must not be empty")]
1681    EmptyMessage,
1682
1683    /// The child rule at `child_index` has `level <= self.level`,
1684    /// violating the "children must nest deeper than the parent"
1685    /// invariant of the hierarchical indentation-based DSL.
1686    #[error(
1687        "child rule at index {child_index} has level {child_level}, \
1688         must be greater than parent level {parent_level}"
1689    )]
1690    InvalidChildLevel {
1691        /// Index of the offending child in `self.children`.
1692        child_index: usize,
1693        /// Level of the child rule.
1694        child_level: u32,
1695        /// Level of the parent rule.
1696        parent_level: u32,
1697    },
1698
1699    /// Rule `level` exceeds the maximum supported depth. The limit is a
1700    /// hardening mechanism against stack overflow during deep recursion;
1701    /// libmagic files in the wild rarely go beyond 10 levels.
1702    #[error("rule level {level} exceeds maximum supported depth {max}")]
1703    LevelTooDeep {
1704        /// The invalid level value.
1705        level: u32,
1706        /// The maximum allowed depth.
1707        max: u32,
1708    },
1709}
1710
1711impl MagicRule {
1712    /// Hard structural ceiling on rule `level`.
1713    ///
1714    /// This is a conservative upper bound enforced by
1715    /// [`MagicRule::validate`] to keep the AST shape sane: real
1716    /// magic files in the wild rarely exceed ~10 levels of nesting,
1717    /// so rejecting rules with `level > 1000` catches obviously
1718    /// pathological input at construction time without constraining
1719    /// any legitimate rule.
1720    ///
1721    /// This ceiling is **independent of** the evaluator's
1722    /// `EvaluationConfig::max_recursion_depth` (default 20), which
1723    /// is the *runtime* recursion guard applied during rule
1724    /// evaluation. The evaluator limit is the first one that fires
1725    /// in practice -- a rule tree with 50 levels passes this
1726    /// structural check but is aborted by the evaluator long before
1727    /// reaching `MAX_LEVEL`. The two limits serve different purposes:
1728    /// `MAX_LEVEL` is an AST-shape sanity check, and
1729    /// `max_recursion_depth` is a per-evaluation resource bound.
1730    pub const MAX_LEVEL: u32 = 1000;
1731
1732    /// Construct a top-level rule with no children and no strength
1733    /// modifier.
1734    ///
1735    /// This is the most common constructor for programmatically building
1736    /// rules outside the parser. To add children, mutate
1737    /// [`MagicRule::children`] directly, or use [`MagicRule::with_children`].
1738    /// To set a strength modifier, use
1739    /// [`MagicRule::with_strength_modifier`].
1740    ///
1741    /// # Examples
1742    ///
1743    /// ```rust
1744    /// use libmagic_rs::{MagicRule, OffsetSpec, Operator, TypeKind, Value};
1745    ///
1746    /// let rule = MagicRule::new(
1747    ///     OffsetSpec::Absolute(0),
1748    ///     TypeKind::Byte { signed: false },
1749    ///     Operator::Equal,
1750    ///     Value::Uint(0x7f),
1751    ///     "ELF magic byte".to_string(),
1752    /// );
1753    /// assert_eq!(rule.level, 0);
1754    /// assert!(rule.children.is_empty());
1755    /// assert!(rule.validate().is_ok());
1756    /// ```
1757    #[must_use]
1758    pub fn new(
1759        offset: OffsetSpec,
1760        typ: TypeKind,
1761        op: Operator,
1762        value: Value,
1763        message: String,
1764    ) -> Self {
1765        Self {
1766            offset,
1767            typ,
1768            op,
1769            value,
1770            message,
1771            children: vec![],
1772            level: 0,
1773            strength_modifier: None,
1774            value_transform: None,
1775        }
1776    }
1777
1778    /// Replace `self.children` with the given children and return the
1779    /// modified rule. Builder-style for chaining.
1780    #[must_use]
1781    pub fn with_children(mut self, children: Vec<MagicRule>) -> Self {
1782        self.children = children;
1783        self
1784    }
1785
1786    /// Set `self.strength_modifier` to the given value and return the
1787    /// modified rule. Builder-style for chaining.
1788    #[must_use]
1789    pub const fn with_strength_modifier(mut self, modifier: StrengthModifier) -> Self {
1790        self.strength_modifier = Some(modifier);
1791        self
1792    }
1793
1794    /// Set `self.level` to the given value and return the modified rule.
1795    /// Builder-style for chaining; typically used only when constructing
1796    /// child rules programmatically.
1797    #[must_use]
1798    pub const fn with_level(mut self, level: u32) -> Self {
1799        self.level = level;
1800        self
1801    }
1802
1803    /// Validate structural invariants of the rule.
1804    ///
1805    /// This checks invariants that the parser enforces automatically but
1806    /// that programmatic constructors (especially via serde deserialize)
1807    /// can violate:
1808    ///
1809    /// * Message must not be empty.
1810    /// * `level` must not exceed [`Self::MAX_LEVEL`].
1811    /// * Every child's `level` must be strictly greater than
1812    ///   `self.level`, and each child must recursively validate.
1813    ///
1814    /// This does *not* validate that `value` is shape-compatible with
1815    /// `typ` (e.g., a `Value::Uint` against a `TypeKind::String`); such
1816    /// mismatches are coerced or rejected by the evaluator at match time.
1817    ///
1818    /// # Errors
1819    ///
1820    /// Returns [`MagicRuleValidationError`] describing the first
1821    /// invariant violation encountered.
1822    ///
1823    /// # Examples
1824    ///
1825    /// ```rust
1826    /// use libmagic_rs::{MagicRule, OffsetSpec, Operator, TypeKind, Value};
1827    ///
1828    /// let rule = MagicRule::new(
1829    ///     OffsetSpec::Absolute(0),
1830    ///     TypeKind::Byte { signed: false },
1831    ///     Operator::Equal,
1832    ///     Value::Uint(0),
1833    ///     "zero byte".to_string(),
1834    /// );
1835    /// assert!(rule.validate().is_ok());
1836    /// ```
1837    pub fn validate(&self) -> Result<(), MagicRuleValidationError> {
1838        if self.message.is_empty() {
1839            return Err(MagicRuleValidationError::EmptyMessage);
1840        }
1841        if self.level > Self::MAX_LEVEL {
1842            return Err(MagicRuleValidationError::LevelTooDeep {
1843                level: self.level,
1844                max: Self::MAX_LEVEL,
1845            });
1846        }
1847        for (child_index, child) in self.children.iter().enumerate() {
1848            if child.level <= self.level {
1849                return Err(MagicRuleValidationError::InvalidChildLevel {
1850                    child_index,
1851                    child_level: child.level,
1852                    parent_level: self.level,
1853                });
1854            }
1855            child.validate()?;
1856        }
1857        Ok(())
1858    }
1859}
1860
1861#[cfg(test)]
1862mod tests {
1863    // Restriction lints without an allow-*-in-tests config option;
1864    // non-ASCII test data exercises string value handling.
1865    #![allow(clippy::non_ascii_literal)]
1866
1867    use super::*;
1868
1869    #[test]
1870    fn test_magic_rule_new_defaults() {
1871        let rule = MagicRule::new(
1872            OffsetSpec::Absolute(0),
1873            TypeKind::Byte { signed: false },
1874            Operator::Equal,
1875            Value::Uint(0x7f),
1876            "ELF".to_string(),
1877        );
1878        assert_eq!(rule.level, 0);
1879        assert!(rule.children.is_empty());
1880        assert!(rule.strength_modifier.is_none());
1881        assert!(rule.validate().is_ok());
1882    }
1883
1884    #[test]
1885    fn test_magic_rule_builder_chain() {
1886        let child = MagicRule::new(
1887            OffsetSpec::Absolute(4),
1888            TypeKind::Byte { signed: false },
1889            Operator::Equal,
1890            Value::Uint(2),
1891            "64-bit".to_string(),
1892        )
1893        .with_level(1);
1894        let parent = MagicRule::new(
1895            OffsetSpec::Absolute(0),
1896            TypeKind::Byte { signed: false },
1897            Operator::Equal,
1898            Value::Uint(0x7f),
1899            "ELF".to_string(),
1900        )
1901        .with_children(vec![child])
1902        .with_strength_modifier(StrengthModifier::Add(10));
1903        assert_eq!(parent.children.len(), 1);
1904        assert_eq!(parent.strength_modifier, Some(StrengthModifier::Add(10)));
1905        assert!(parent.validate().is_ok());
1906    }
1907
1908    #[test]
1909    fn test_magic_rule_validate_empty_message_rejected() {
1910        let rule = MagicRule::new(
1911            OffsetSpec::Absolute(0),
1912            TypeKind::Byte { signed: false },
1913            Operator::Equal,
1914            Value::Uint(0),
1915            String::new(),
1916        );
1917        assert_eq!(rule.validate(), Err(MagicRuleValidationError::EmptyMessage));
1918    }
1919
1920    #[test]
1921    fn test_magic_rule_validate_child_level_must_be_deeper() {
1922        let child_same_level = MagicRule::new(
1923            OffsetSpec::Absolute(4),
1924            TypeKind::Byte { signed: false },
1925            Operator::Equal,
1926            Value::Uint(2),
1927            "child".to_string(),
1928        ); // level = 0, same as parent
1929        let parent = MagicRule::new(
1930            OffsetSpec::Absolute(0),
1931            TypeKind::Byte { signed: false },
1932            Operator::Equal,
1933            Value::Uint(0x7f),
1934            "parent".to_string(),
1935        )
1936        .with_children(vec![child_same_level]);
1937        assert_eq!(
1938            parent.validate(),
1939            Err(MagicRuleValidationError::InvalidChildLevel {
1940                child_index: 0,
1941                child_level: 0,
1942                parent_level: 0,
1943            })
1944        );
1945    }
1946
1947    #[test]
1948    fn test_magic_rule_validate_level_too_deep() {
1949        let rule = MagicRule::new(
1950            OffsetSpec::Absolute(0),
1951            TypeKind::Byte { signed: false },
1952            Operator::Equal,
1953            Value::Uint(0),
1954            "deep".to_string(),
1955        )
1956        .with_level(MagicRule::MAX_LEVEL + 1);
1957        assert_eq!(
1958            rule.validate(),
1959            Err(MagicRuleValidationError::LevelTooDeep {
1960                level: MagicRule::MAX_LEVEL + 1,
1961                max: MagicRule::MAX_LEVEL,
1962            })
1963        );
1964    }
1965
1966    #[test]
1967    fn test_offset_spec_absolute() {
1968        let offset = OffsetSpec::Absolute(42);
1969        assert_eq!(offset, OffsetSpec::Absolute(42));
1970
1971        // Test negative offset
1972        let negative = OffsetSpec::Absolute(-10);
1973        assert_eq!(negative, OffsetSpec::Absolute(-10));
1974    }
1975
1976    #[test]
1977    fn test_offset_spec_indirect() {
1978        let indirect = OffsetSpec::Indirect {
1979            base_offset: 0x20,
1980            base_relative: false,
1981            pointer_type: TypeKind::Long {
1982                endian: Endianness::Little,
1983                signed: false,
1984            },
1985            adjustment: 4,
1986            adjustment_op: IndirectAdjustmentOp::Add,
1987            result_relative: false,
1988            endian: Endianness::Little,
1989        };
1990
1991        match indirect {
1992            OffsetSpec::Indirect {
1993                base_offset,
1994                adjustment,
1995                ..
1996            } => {
1997                assert_eq!(base_offset, 0x20);
1998                assert_eq!(adjustment, 4);
1999            }
2000            _ => panic!("Expected Indirect variant"),
2001        }
2002    }
2003
2004    #[test]
2005    fn test_offset_spec_relative() {
2006        let relative = OffsetSpec::Relative(8);
2007        assert_eq!(relative, OffsetSpec::Relative(8));
2008
2009        // Test negative relative offset
2010        let negative_relative = OffsetSpec::Relative(-4);
2011        assert_eq!(negative_relative, OffsetSpec::Relative(-4));
2012    }
2013
2014    #[test]
2015    fn test_offset_spec_from_end() {
2016        let from_end = OffsetSpec::FromEnd(-16);
2017        assert_eq!(from_end, OffsetSpec::FromEnd(-16));
2018
2019        // Test positive from_end (though unusual)
2020        let positive_from_end = OffsetSpec::FromEnd(8);
2021        assert_eq!(positive_from_end, OffsetSpec::FromEnd(8));
2022    }
2023
2024    #[test]
2025    fn test_offset_spec_debug() {
2026        let offset = OffsetSpec::Absolute(100);
2027        let debug_str = format!("{offset:?}");
2028        assert!(debug_str.contains("Absolute"));
2029        assert!(debug_str.contains("100"));
2030    }
2031
2032    #[test]
2033    fn test_offset_spec_clone() {
2034        let original = OffsetSpec::Indirect {
2035            base_offset: 0x10,
2036            base_relative: false,
2037            pointer_type: TypeKind::Short {
2038                endian: Endianness::Big,
2039                signed: true,
2040            },
2041            adjustment: -2,
2042            adjustment_op: IndirectAdjustmentOp::Add,
2043            result_relative: false,
2044            endian: Endianness::Big,
2045        };
2046
2047        let cloned = original.clone();
2048        assert_eq!(original, cloned);
2049    }
2050
2051    #[test]
2052    fn test_offset_spec_serialization() {
2053        let offset = OffsetSpec::Absolute(42);
2054
2055        // Test JSON serialization
2056        let json = serde_json::to_string(&offset).expect("Failed to serialize");
2057        let deserialized: OffsetSpec = serde_json::from_str(&json).expect("Failed to deserialize");
2058
2059        assert_eq!(offset, deserialized);
2060    }
2061
2062    #[test]
2063    fn test_offset_spec_indirect_serialization() {
2064        let indirect = OffsetSpec::Indirect {
2065            base_offset: 0x100,
2066            base_relative: false,
2067            pointer_type: TypeKind::Long {
2068                endian: Endianness::Native,
2069                signed: false,
2070            },
2071            adjustment: 12,
2072            adjustment_op: IndirectAdjustmentOp::Add,
2073            result_relative: false,
2074            endian: Endianness::Native,
2075        };
2076
2077        // Test JSON serialization for complex variant
2078        let json = serde_json::to_string(&indirect).expect("Failed to serialize");
2079        let deserialized: OffsetSpec = serde_json::from_str(&json).expect("Failed to deserialize");
2080
2081        assert_eq!(indirect, deserialized);
2082    }
2083
2084    #[test]
2085    fn test_all_offset_spec_variants() {
2086        let variants = [
2087            OffsetSpec::Absolute(0),
2088            OffsetSpec::Absolute(-100),
2089            OffsetSpec::Indirect {
2090                base_offset: 0x20,
2091                base_relative: false,
2092                pointer_type: TypeKind::Byte { signed: true },
2093                adjustment: 0,
2094                adjustment_op: IndirectAdjustmentOp::Add,
2095                result_relative: false,
2096                endian: Endianness::Little,
2097            },
2098            OffsetSpec::Relative(50),
2099            OffsetSpec::Relative(-25),
2100            OffsetSpec::FromEnd(-8),
2101            OffsetSpec::FromEnd(4),
2102        ];
2103
2104        // Test that all variants can be created and are distinct
2105        for (i, variant) in variants.iter().enumerate() {
2106            for (j, other) in variants.iter().enumerate() {
2107                if i != j {
2108                    assert_ne!(
2109                        variant, other,
2110                        "Variants at indices {i} and {j} should be different"
2111                    );
2112                }
2113            }
2114        }
2115    }
2116
2117    #[test]
2118    fn test_endianness_variants() {
2119        let endianness_values = vec![Endianness::Little, Endianness::Big, Endianness::Native];
2120
2121        for endian in endianness_values {
2122            let indirect = OffsetSpec::Indirect {
2123                base_offset: 0,
2124                base_relative: false,
2125                pointer_type: TypeKind::Long {
2126                    endian,
2127                    signed: false,
2128                },
2129                adjustment: 0,
2130                adjustment_op: IndirectAdjustmentOp::Add,
2131                result_relative: false,
2132                endian,
2133            };
2134
2135            // Verify the endianness is preserved
2136            match indirect {
2137                OffsetSpec::Indirect {
2138                    endian: actual_endian,
2139                    ..
2140                } => {
2141                    assert_eq!(endian, actual_endian);
2142                }
2143                _ => panic!("Expected Indirect variant"),
2144            }
2145        }
2146    }
2147
2148    // Value enum tests
2149    #[test]
2150    fn test_value_uint() {
2151        let value = Value::Uint(42);
2152        assert_eq!(value, Value::Uint(42));
2153
2154        // Test large values
2155        let large_value = Value::Uint(u64::MAX);
2156        assert_eq!(large_value, Value::Uint(u64::MAX));
2157    }
2158
2159    #[test]
2160    fn test_value_int() {
2161        let positive = Value::Int(100);
2162        assert_eq!(positive, Value::Int(100));
2163
2164        let negative = Value::Int(-50);
2165        assert_eq!(negative, Value::Int(-50));
2166
2167        // Test extreme values
2168        let max_int = Value::Int(i64::MAX);
2169        let min_int = Value::Int(i64::MIN);
2170        assert_eq!(max_int, Value::Int(i64::MAX));
2171        assert_eq!(min_int, Value::Int(i64::MIN));
2172    }
2173
2174    #[test]
2175    fn test_value_bytes() {
2176        let empty_bytes = Value::Bytes(vec![]);
2177        assert_eq!(empty_bytes, Value::Bytes(vec![]));
2178
2179        let some_bytes = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
2180        assert_eq!(some_bytes, Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]));
2181
2182        // Test that different byte sequences are not equal
2183        let other_bytes = Value::Bytes(vec![0x50, 0x4b, 0x03, 0x04]);
2184        assert_ne!(some_bytes, other_bytes);
2185    }
2186
2187    #[test]
2188    fn test_value_string() {
2189        let empty_string = Value::String(String::new());
2190        assert_eq!(empty_string, Value::String(String::new()));
2191
2192        let hello = Value::String("Hello, World!".to_string());
2193        assert_eq!(hello, Value::String("Hello, World!".to_string()));
2194
2195        // Test Unicode strings
2196        let unicode = Value::String("🦀 Rust".to_string());
2197        assert_eq!(unicode, Value::String("🦀 Rust".to_string()));
2198    }
2199
2200    #[test]
2201    fn test_value_comparison() {
2202        // Test that different value types are not equal
2203        let uint_val = Value::Uint(42);
2204        let int_val = Value::Int(42);
2205        let float_val = Value::Float(42.0);
2206        let bytes_val = Value::Bytes(vec![42]);
2207        let string_val = Value::String("42".to_string());
2208
2209        assert_ne!(uint_val, int_val);
2210        assert_ne!(uint_val, float_val);
2211        assert_ne!(uint_val, bytes_val);
2212        assert_ne!(uint_val, string_val);
2213        assert_ne!(int_val, float_val);
2214        assert_ne!(int_val, bytes_val);
2215        assert_ne!(int_val, string_val);
2216        assert_ne!(float_val, bytes_val);
2217        assert_ne!(float_val, string_val);
2218        assert_ne!(bytes_val, string_val);
2219    }
2220
2221    #[test]
2222    fn test_value_debug() {
2223        let uint_val = Value::Uint(123);
2224        let debug_str = format!("{uint_val:?}");
2225        assert!(debug_str.contains("Uint"));
2226        assert!(debug_str.contains("123"));
2227
2228        let string_val = Value::String("test".to_string());
2229        let debug_str = format!("{string_val:?}");
2230        assert!(debug_str.contains("String"));
2231        assert!(debug_str.contains("test"));
2232    }
2233
2234    #[test]
2235    fn test_value_clone() {
2236        let original = Value::Bytes(vec![1, 2, 3, 4]);
2237        let cloned = original.clone();
2238        assert_eq!(original, cloned);
2239
2240        // Verify they are independent copies
2241        match (original, cloned) {
2242            (Value::Bytes(orig_bytes), Value::Bytes(cloned_bytes)) => {
2243                assert_eq!(orig_bytes, cloned_bytes);
2244                // They should have the same content but be different Vec instances
2245            }
2246            _ => panic!("Expected Bytes variants"),
2247        }
2248    }
2249
2250    #[test]
2251    fn test_value_float() {
2252        let value = Value::Float(3.125);
2253        assert_eq!(value, Value::Float(3.125));
2254
2255        let negative = Value::Float(-1.5);
2256        assert_eq!(negative, Value::Float(-1.5));
2257
2258        let zero = Value::Float(0.0);
2259        assert_eq!(zero, Value::Float(0.0));
2260    }
2261
2262    #[test]
2263    fn test_value_serialization() {
2264        let values = vec![
2265            Value::Uint(42),
2266            Value::Int(-100),
2267            Value::Float(3.125),
2268            Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
2269            Value::String("ELF executable".to_string()),
2270        ];
2271
2272        for value in values {
2273            // Test JSON serialization
2274            let json = serde_json::to_string(&value).expect("Failed to serialize Value");
2275            let deserialized: Value =
2276                serde_json::from_str(&json).expect("Failed to deserialize Value");
2277            assert_eq!(value, deserialized);
2278        }
2279    }
2280
2281    #[test]
2282    fn test_value_serialization_edge_cases() {
2283        // Test empty collections
2284        let empty_bytes = Value::Bytes(vec![]);
2285        let json = serde_json::to_string(&empty_bytes).expect("Failed to serialize empty bytes");
2286        let deserialized: Value =
2287            serde_json::from_str(&json).expect("Failed to deserialize empty bytes");
2288        assert_eq!(empty_bytes, deserialized);
2289
2290        let empty_string = Value::String(String::new());
2291        let json = serde_json::to_string(&empty_string).expect("Failed to serialize empty string");
2292        let deserialized: Value =
2293            serde_json::from_str(&json).expect("Failed to deserialize empty string");
2294        assert_eq!(empty_string, deserialized);
2295
2296        // Test extreme values
2297        let max_uint = Value::Uint(u64::MAX);
2298        let json = serde_json::to_string(&max_uint).expect("Failed to serialize max uint");
2299        let deserialized: Value =
2300            serde_json::from_str(&json).expect("Failed to deserialize max uint");
2301        assert_eq!(max_uint, deserialized);
2302
2303        let min_int = Value::Int(i64::MIN);
2304        let json = serde_json::to_string(&min_int).expect("Failed to serialize min int");
2305        let deserialized: Value =
2306            serde_json::from_str(&json).expect("Failed to deserialize min int");
2307        assert_eq!(min_int, deserialized);
2308    }
2309
2310    // TypeKind tests
2311    #[test]
2312    fn test_type_kind_byte() {
2313        let byte_type = TypeKind::Byte { signed: true };
2314        assert_eq!(byte_type, TypeKind::Byte { signed: true });
2315    }
2316
2317    #[test]
2318    fn test_type_kind_short() {
2319        let short_little_endian = TypeKind::Short {
2320            endian: Endianness::Little,
2321            signed: false,
2322        };
2323        let short_big_endian = TypeKind::Short {
2324            endian: Endianness::Big,
2325            signed: true,
2326        };
2327
2328        assert_ne!(short_little_endian, short_big_endian);
2329        assert_eq!(short_little_endian, short_little_endian.clone());
2330    }
2331
2332    #[test]
2333    fn test_type_kind_long() {
2334        let long_native = TypeKind::Long {
2335            endian: Endianness::Native,
2336            signed: true,
2337        };
2338
2339        match long_native {
2340            TypeKind::Long { endian, signed } => {
2341                assert_eq!(endian, Endianness::Native);
2342                assert!(signed);
2343            }
2344            _ => panic!("Expected Long variant"),
2345        }
2346    }
2347
2348    #[test]
2349    fn test_type_kind_string() {
2350        let unlimited_string = TypeKind::String {
2351            max_length: None,
2352            flags: StringFlags::default(),
2353        };
2354        let limited_string = TypeKind::String {
2355            max_length: Some(256),
2356            flags: StringFlags::default(),
2357        };
2358
2359        assert_ne!(unlimited_string, limited_string);
2360        assert_eq!(unlimited_string, unlimited_string.clone());
2361    }
2362
2363    #[test]
2364    fn test_type_kind_serialization() {
2365        let types = vec![
2366            TypeKind::Byte { signed: true },
2367            TypeKind::Short {
2368                endian: Endianness::Little,
2369                signed: false,
2370            },
2371            TypeKind::Long {
2372                endian: Endianness::Big,
2373                signed: true,
2374            },
2375            TypeKind::Quad {
2376                endian: Endianness::Little,
2377                signed: false,
2378            },
2379            TypeKind::Quad {
2380                endian: Endianness::Big,
2381                signed: true,
2382            },
2383            TypeKind::Float {
2384                endian: Endianness::Native,
2385            },
2386            TypeKind::Float {
2387                endian: Endianness::Big,
2388            },
2389            TypeKind::Double {
2390                endian: Endianness::Little,
2391            },
2392            TypeKind::Double {
2393                endian: Endianness::Native,
2394            },
2395            TypeKind::Date {
2396                endian: Endianness::Big,
2397                utc: true,
2398            },
2399            TypeKind::Date {
2400                endian: Endianness::Little,
2401                utc: false,
2402            },
2403            TypeKind::QDate {
2404                endian: Endianness::Native,
2405                utc: true,
2406            },
2407            TypeKind::QDate {
2408                endian: Endianness::Big,
2409                utc: false,
2410            },
2411            TypeKind::String {
2412                max_length: None,
2413                flags: StringFlags::default(),
2414            },
2415            TypeKind::String {
2416                max_length: Some(128),
2417                flags: StringFlags::default(),
2418            },
2419            TypeKind::PString {
2420                max_length: None,
2421                length_width: PStringLengthWidth::OneByte,
2422                length_includes_itself: false,
2423            },
2424            TypeKind::PString {
2425                max_length: Some(64),
2426                length_width: PStringLengthWidth::OneByte,
2427                length_includes_itself: false,
2428            },
2429            TypeKind::PString {
2430                max_length: None,
2431                length_width: PStringLengthWidth::TwoByteBE,
2432                length_includes_itself: true,
2433            },
2434            TypeKind::PString {
2435                max_length: Some(128),
2436                length_width: PStringLengthWidth::FourByteLE,
2437                length_includes_itself: false,
2438            },
2439        ];
2440
2441        for typ in types {
2442            let json = serde_json::to_string(&typ).expect("Failed to serialize TypeKind");
2443            let deserialized: TypeKind =
2444                serde_json::from_str(&json).expect("Failed to deserialize TypeKind");
2445            assert_eq!(typ, deserialized);
2446        }
2447    }
2448
2449    // Operator tests
2450    #[test]
2451    fn test_operator_variants() {
2452        let operators = [
2453            Operator::Equal,
2454            Operator::NotEqual,
2455            Operator::BitwiseAnd,
2456            Operator::BitwiseXor,
2457            Operator::BitwiseNot,
2458            Operator::AnyValue,
2459        ];
2460
2461        for (i, op) in operators.iter().enumerate() {
2462            for (j, other) in operators.iter().enumerate() {
2463                if i == j {
2464                    assert_eq!(op, other);
2465                } else {
2466                    assert_ne!(op, other);
2467                }
2468            }
2469        }
2470    }
2471
2472    #[test]
2473    fn test_operator_serialization() {
2474        let operators = vec![
2475            Operator::Equal,
2476            Operator::NotEqual,
2477            Operator::BitwiseAnd,
2478            Operator::BitwiseXor,
2479            Operator::BitwiseNot,
2480            Operator::AnyValue,
2481        ];
2482
2483        for op in operators {
2484            let json = serde_json::to_string(&op).expect("Failed to serialize Operator");
2485            let deserialized: Operator =
2486                serde_json::from_str(&json).expect("Failed to deserialize Operator");
2487            assert_eq!(op, deserialized);
2488        }
2489    }
2490
2491    // MagicRule tests
2492    #[test]
2493    fn test_magic_rule_creation() {
2494        let rule = MagicRule {
2495            offset: OffsetSpec::Absolute(0),
2496            typ: TypeKind::Byte { signed: true },
2497            op: Operator::Equal,
2498            value: Value::Uint(0x7f),
2499            message: "ELF magic".to_string(),
2500            children: vec![],
2501            level: 0,
2502            strength_modifier: None,
2503            value_transform: None,
2504        };
2505
2506        assert_eq!(rule.message, "ELF magic");
2507        assert_eq!(rule.level, 0);
2508        assert!(rule.children.is_empty());
2509    }
2510
2511    #[test]
2512    fn test_magic_rule_with_children() {
2513        let child_rule = MagicRule {
2514            offset: OffsetSpec::Absolute(4),
2515            typ: TypeKind::Byte { signed: true },
2516            op: Operator::Equal,
2517            value: Value::Uint(1),
2518            message: "32-bit".to_string(),
2519            children: vec![],
2520            level: 1,
2521            strength_modifier: None,
2522            value_transform: None,
2523        };
2524
2525        let parent_rule = MagicRule {
2526            offset: OffsetSpec::Absolute(0),
2527            typ: TypeKind::Long {
2528                endian: Endianness::Little,
2529                signed: false,
2530            },
2531            op: Operator::Equal,
2532            value: Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
2533            message: "ELF executable".to_string(),
2534            children: vec![child_rule],
2535            level: 0,
2536            strength_modifier: None,
2537            value_transform: None,
2538        };
2539
2540        assert_eq!(parent_rule.children.len(), 1);
2541        assert_eq!(parent_rule.children[0].level, 1);
2542        assert_eq!(parent_rule.children[0].message, "32-bit");
2543    }
2544
2545    #[test]
2546    fn test_magic_rule_serialization() {
2547        let rule = MagicRule {
2548            offset: OffsetSpec::Absolute(16),
2549            typ: TypeKind::Short {
2550                endian: Endianness::Little,
2551                signed: false,
2552            },
2553            op: Operator::NotEqual,
2554            value: Value::Uint(0),
2555            message: "Non-zero short value".to_string(),
2556            children: vec![],
2557            level: 2,
2558            strength_modifier: None,
2559            value_transform: None,
2560        };
2561
2562        let json = serde_json::to_string(&rule).expect("Failed to serialize MagicRule");
2563        let deserialized: MagicRule =
2564            serde_json::from_str(&json).expect("Failed to deserialize MagicRule");
2565
2566        assert_eq!(rule.message, deserialized.message);
2567        assert_eq!(rule.level, deserialized.level);
2568        assert_eq!(rule.children.len(), deserialized.children.len());
2569    }
2570
2571    // StrengthModifier tests
2572    #[test]
2573    fn test_strength_modifier_variants() {
2574        let add = StrengthModifier::Add(10);
2575        let sub = StrengthModifier::Subtract(5);
2576        let mul = StrengthModifier::Multiply(2);
2577        let div = StrengthModifier::Divide(2);
2578        let set = StrengthModifier::Set(50);
2579
2580        // Test that each variant has the correct inner value
2581        assert_eq!(add, StrengthModifier::Add(10));
2582        assert_eq!(sub, StrengthModifier::Subtract(5));
2583        assert_eq!(mul, StrengthModifier::Multiply(2));
2584        assert_eq!(div, StrengthModifier::Divide(2));
2585        assert_eq!(set, StrengthModifier::Set(50));
2586
2587        // Test that different variants are not equal
2588        assert_ne!(add, sub);
2589        assert_ne!(mul, div);
2590        assert_ne!(set, add);
2591    }
2592
2593    #[test]
2594    fn test_strength_modifier_negative_values() {
2595        let add_negative = StrengthModifier::Add(-10);
2596        let sub_negative = StrengthModifier::Subtract(-5);
2597        let set_negative = StrengthModifier::Set(-50);
2598
2599        assert_eq!(add_negative, StrengthModifier::Add(-10));
2600        assert_eq!(sub_negative, StrengthModifier::Subtract(-5));
2601        assert_eq!(set_negative, StrengthModifier::Set(-50));
2602    }
2603
2604    #[test]
2605    fn test_strength_modifier_serialization() {
2606        let modifiers = vec![
2607            StrengthModifier::Add(10),
2608            StrengthModifier::Subtract(5),
2609            StrengthModifier::Multiply(2),
2610            StrengthModifier::Divide(3),
2611            StrengthModifier::Set(100),
2612        ];
2613
2614        for modifier in modifiers {
2615            let json =
2616                serde_json::to_string(&modifier).expect("Failed to serialize StrengthModifier");
2617            let deserialized: StrengthModifier =
2618                serde_json::from_str(&json).expect("Failed to deserialize StrengthModifier");
2619            assert_eq!(modifier, deserialized);
2620        }
2621    }
2622
2623    #[test]
2624    fn test_strength_modifier_debug() {
2625        let modifier = StrengthModifier::Add(25);
2626        let debug_str = format!("{modifier:?}");
2627        assert!(debug_str.contains("Add"));
2628        assert!(debug_str.contains("25"));
2629    }
2630
2631    #[test]
2632    fn test_strength_modifier_clone() {
2633        let original = StrengthModifier::Multiply(4);
2634        let cloned = original;
2635        assert_eq!(original, cloned);
2636    }
2637
2638    #[test]
2639    fn test_magic_rule_with_strength_modifier() {
2640        let rule = MagicRule {
2641            offset: OffsetSpec::Absolute(0),
2642            typ: TypeKind::Byte { signed: true },
2643            op: Operator::Equal,
2644            value: Value::Uint(0x7f),
2645            message: "ELF magic".to_string(),
2646            children: vec![],
2647            level: 0,
2648            strength_modifier: Some(StrengthModifier::Add(20)),
2649            value_transform: None,
2650        };
2651
2652        assert_eq!(rule.strength_modifier, Some(StrengthModifier::Add(20)));
2653
2654        // Test serialization with strength_modifier
2655        let json = serde_json::to_string(&rule).expect("Failed to serialize MagicRule");
2656        let deserialized: MagicRule =
2657            serde_json::from_str(&json).expect("Failed to deserialize MagicRule");
2658        assert_eq!(rule.strength_modifier, deserialized.strength_modifier);
2659    }
2660
2661    #[test]
2662    fn test_magic_rule_without_strength_modifier() {
2663        let rule = MagicRule {
2664            offset: OffsetSpec::Absolute(0),
2665            typ: TypeKind::Byte { signed: true },
2666            op: Operator::Equal,
2667            value: Value::Uint(0x7f),
2668            message: "ELF magic".to_string(),
2669            children: vec![],
2670            level: 0,
2671            strength_modifier: None,
2672            value_transform: None,
2673        };
2674
2675        assert_eq!(rule.strength_modifier, None);
2676    }
2677
2678    // MetaType tests
2679    #[test]
2680    fn test_meta_type_variants_debug_clone_eq() {
2681        let cases = [
2682            MetaType::Default,
2683            MetaType::Clear,
2684            MetaType::Indirect,
2685            MetaType::Offset,
2686            MetaType::Name("part2".to_string()),
2687            MetaType::Use {
2688                name: "part2".to_string(),
2689                flip_endian: false,
2690            },
2691            // A `\^`-flipped use must be distinct from the plain form so the
2692            // endian-flip flag participates in equality/serde/round-trip.
2693            MetaType::Use {
2694                name: "part2".to_string(),
2695                flip_endian: true,
2696            },
2697        ];
2698
2699        for (i, variant) in cases.iter().enumerate() {
2700            // Debug formatting is non-empty
2701            let debug_str = format!("{variant:?}");
2702            assert!(
2703                !debug_str.is_empty(),
2704                "Debug format must be non-empty for variant at index {i}"
2705            );
2706
2707            // Clone round-trip preserves equality
2708            let cloned = variant.clone();
2709            assert_eq!(
2710                variant, &cloned,
2711                "Clone must preserve equality for variant at index {i}"
2712            );
2713
2714            // Distinct variants are not equal
2715            for (j, other) in cases.iter().enumerate() {
2716                if i == j {
2717                    assert_eq!(variant, other);
2718                } else {
2719                    assert_ne!(
2720                        variant, other,
2721                        "Variants at indices {i} and {j} must differ"
2722                    );
2723                }
2724            }
2725        }
2726    }
2727
2728    #[test]
2729    fn test_meta_type_serde_roundtrip() {
2730        let cases = [
2731            MetaType::Default,
2732            MetaType::Clear,
2733            MetaType::Indirect,
2734            MetaType::Offset,
2735            MetaType::Name("foo".to_string()),
2736            MetaType::Use {
2737                name: "bar".to_string(),
2738                flip_endian: false,
2739            },
2740            MetaType::Use {
2741                name: "bar".to_string(),
2742                flip_endian: true,
2743            },
2744        ];
2745
2746        for variant in cases {
2747            let json = serde_json::to_string(&variant).expect("serialize MetaType");
2748            let deserialized: MetaType = serde_json::from_str(&json).expect("deserialize MetaType");
2749            assert_eq!(variant, deserialized);
2750        }
2751    }
2752
2753    #[test]
2754    fn test_type_kind_meta_bit_width_is_none() {
2755        let cases = [
2756            MetaType::Default,
2757            MetaType::Clear,
2758            MetaType::Indirect,
2759            MetaType::Offset,
2760            MetaType::Name("x".to_string()),
2761            MetaType::Use {
2762                name: "x".to_string(),
2763                flip_endian: false,
2764            },
2765            MetaType::Use {
2766                name: "x".to_string(),
2767                flip_endian: true,
2768            },
2769        ];
2770        for meta in cases {
2771            let kind = TypeKind::Meta(meta);
2772            assert_eq!(
2773                kind.bit_width(),
2774                None,
2775                "TypeKind::Meta must have no bit width: {kind:?}"
2776            );
2777        }
2778    }
2779}