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` or `/s` 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///
776/// let case_and_start = RegexFlags::default()
777///     .with_case_insensitive(true)
778///     .with_start_offset(true);
779/// assert!(case_and_start.case_insensitive);
780/// assert!(case_and_start.start_offset);
781/// ```
782#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
783#[non_exhaustive]
784pub struct RegexFlags {
785    /// `/c` -- case-insensitive matching. When `true`, ASCII letter
786    /// casing is ignored during pattern matching.
787    pub case_insensitive: bool,
788    /// `/s` -- advance the GNU `file` previous-match anchor to the start
789    /// of the matched region instead of its end. Matches libmagic's
790    /// `REGEX_OFFSET_START` flag, which zeros the length contribution in
791    /// `moffset()` for `FILE_REGEX`. Useful for chaining child rules that
792    /// need to re-match from the position where the parent regex began.
793    pub start_offset: bool,
794}
795
796impl RegexFlags {
797    /// Builder-style setter for [`RegexFlags::case_insensitive`] (`/c`).
798    ///
799    /// Chain after [`RegexFlags::default()`] to construct `RegexFlags`
800    /// values without exhaustive struct literals. If a new flag is
801    /// added to `RegexFlags` in the future, callers using the builder
802    /// form keep compiling; callers using struct literals would need
803    /// an update.
804    #[must_use]
805    pub const fn with_case_insensitive(mut self, value: bool) -> Self {
806        self.case_insensitive = value;
807        self
808    }
809
810    /// Builder-style setter for [`RegexFlags::start_offset`] (`/s`).
811    ///
812    /// Chain after [`RegexFlags::default()`] to construct `RegexFlags`
813    /// values without exhaustive struct literals.
814    #[must_use]
815    pub const fn with_start_offset(mut self, value: bool) -> Self {
816        self.start_offset = value;
817        self
818    }
819}
820
821/// String modifier flags parsed from the `/[cCwWtTbf]` suffix on a `string`
822/// rule.
823///
824/// Mirrors libmagic's `STRING_*` flag bits from `src/file.h`. Each flag
825/// alters how `compare_string_with_flags` walks the pattern and buffer in
826/// parallel. The default (all `false`) preserves byte-exact comparison.
827///
828/// **`/c` vs `/C` are asymmetric**: the pattern character controls
829/// direction. With `/c`, only lowercase pattern chars trigger case-folding
830/// (the file byte is `tolower`'d). With `/C`, only uppercase pattern chars
831/// trigger folding (the file byte is `toupper`'d). Mixed-case patterns
832/// behave intuitively: `/c FoO` matches `FoO`, `Foo`, `FOO` but not
833/// `fOO` (the uppercase `F` is literal). See GOTCHAS S6.5 for the
834/// rationale and `src/softmagic.c` for the canonical libmagic contract.
835///
836/// **`/B` is NOT a string flag** -- it is the `pstring` 1-byte length-width
837/// letter (`PSTRING_1_BE`). `string/B` is rejected at parse time. See
838/// GOTCHAS S6.6.
839///
840/// # Examples
841///
842/// ```
843/// use libmagic_rs::parser::ast::StringFlags;
844///
845/// let plain = StringFlags::default();
846/// assert!(!plain.ignore_lowercase);
847///
848/// let case_insensitive = StringFlags::default().with_ignore_lowercase(true);
849/// assert!(case_insensitive.ignore_lowercase);
850///
851/// let compound = StringFlags::default()
852///     .with_ignore_lowercase(true)
853///     .with_compact_optional_whitespace(true);
854/// assert!(compound.ignore_lowercase);
855/// assert!(compound.compact_optional_whitespace);
856/// ```
857#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
858// libmagic's contract is naturally a bitfield: each flag is a distinct
859// magic(5) letter (/c, /C, /w, /W, /t, /T, /b, /f) with its own STRING_*
860// constant in libmagic src/file.h. Flags compose freely (string/cw is
861// /c plus /w; string/wcCtTbf sets all eight). Folding pairs into enums
862// is possible (whitespace: none|optional|required; case: none|lower|upper)
863// but would obscure the libmagic mapping and produce verbose match arms
864// in every consumer. The bool-per-flag layout mirrors `RegexFlags` and
865// the libmagic source -- the clippy lint is overruled by the design.
866#[allow(clippy::struct_excessive_bools)]
867#[non_exhaustive]
868pub struct StringFlags {
869    /// `/W` -- `STRING_COMPACT_WHITESPACE`. Pattern whitespace requires at
870    /// least one whitespace byte in the file, then any further whitespace
871    /// in the file is consumed greedily.
872    pub compact_whitespace: bool,
873    /// `/w` -- `STRING_COMPACT_OPTIONAL_WHITESPACE`. Pattern whitespace
874    /// matches zero or more whitespace bytes in the file.
875    pub compact_optional_whitespace: bool,
876    /// `/c` -- `STRING_IGNORE_LOWERCASE`. When the pattern char is
877    /// lowercase, the file byte is `to_ascii_lowercase`'d before
878    /// comparison. Uppercase pattern chars are compared literally.
879    pub ignore_lowercase: bool,
880    /// `/C` -- `STRING_IGNORE_UPPERCASE`. When the pattern char is
881    /// uppercase, the file byte is `to_ascii_uppercase`'d before
882    /// comparison. Lowercase pattern chars are compared literally.
883    pub ignore_uppercase: bool,
884    /// `/t` -- `STRING_TEXTTEST`. Pass-selection hint: makes the entry a
885    /// text-pass entry and skips it on a binary buffer when `/b` is not
886    /// also set (GOTCHAS S13.7); no comparison effect.
887    pub text_test: bool,
888    /// `/T` -- `STRING_TRIM`. Trim leading and trailing ASCII whitespace
889    /// from the pattern before comparison. The trim is applied at
890    /// evaluation time (in `read_pattern_match`) so the AST keeps the
891    /// original pattern bytes; the comparison function receives the
892    /// trimmed slice.
893    pub trim: bool,
894    /// `/b` -- `STRING_BINTEST`. Pass-selection hint: skipped on a text
895    /// buffer when `/t` is not also set. It never changes a `string`
896    /// entry's pass, which `/t` alone decides, so `string/bt` is
897    /// text-pass-only (GOTCHAS S13.7); no comparison effect.
898    pub bin_test: bool,
899    /// `/f` -- `STRING_FULL_WORD`. Post-match check that the byte after
900    /// the matched region is either end-of-buffer or a non-word
901    /// character (ASCII alphanumeric or `_`).
902    pub full_word: bool,
903}
904
905impl StringFlags {
906    /// Returns `true` when every flag is `false` (the byte-exact fast
907    /// path). The evaluator dispatcher uses this to skip the
908    /// parallel-walk comparison when no flags are set.
909    #[must_use]
910    pub const fn is_empty(self) -> bool {
911        !self.compact_whitespace
912            && !self.compact_optional_whitespace
913            && !self.ignore_lowercase
914            && !self.ignore_uppercase
915            && !self.text_test
916            && !self.trim
917            && !self.bin_test
918            && !self.full_word
919    }
920
921    /// Builder-style setter for `compact_whitespace` (`/W`).
922    #[must_use]
923    pub const fn with_compact_whitespace(mut self, value: bool) -> Self {
924        self.compact_whitespace = value;
925        self
926    }
927
928    /// Builder-style setter for `compact_optional_whitespace` (`/w`).
929    #[must_use]
930    pub const fn with_compact_optional_whitespace(mut self, value: bool) -> Self {
931        self.compact_optional_whitespace = value;
932        self
933    }
934
935    /// Builder-style setter for `ignore_lowercase` (`/c`).
936    #[must_use]
937    pub const fn with_ignore_lowercase(mut self, value: bool) -> Self {
938        self.ignore_lowercase = value;
939        self
940    }
941
942    /// Builder-style setter for `ignore_uppercase` (`/C`).
943    #[must_use]
944    pub const fn with_ignore_uppercase(mut self, value: bool) -> Self {
945        self.ignore_uppercase = value;
946        self
947    }
948
949    /// Builder-style setter for `text_test` (`/t`).
950    #[must_use]
951    pub const fn with_text_test(mut self, value: bool) -> Self {
952        self.text_test = value;
953        self
954    }
955
956    /// Builder-style setter for `trim` (`/T`).
957    #[must_use]
958    pub const fn with_trim(mut self, value: bool) -> Self {
959        self.trim = value;
960        self
961    }
962
963    /// Builder-style setter for `bin_test` (`/b`).
964    #[must_use]
965    pub const fn with_bin_test(mut self, value: bool) -> Self {
966        self.bin_test = value;
967        self
968    }
969
970    /// Builder-style setter for `full_word` (`/f`).
971    #[must_use]
972    pub const fn with_full_word(mut self, value: bool) -> Self {
973        self.full_word = value;
974        self
975    }
976}
977
978/// Search modifier flags parsed from the `/[sCcWwTtBbf]` suffix on a
979/// `search` rule.
980///
981/// Mirrors [`StringFlags`] for the eight `STRING_*` letters that alter
982/// the literal-pattern comparison (`/c`, `/C`, `/w`, `/W`, `/t`, `/T`,
983/// `/b`, `/f`), plus a search-only `start_anchor` field for `/s` which
984/// shifts the GNU `file` previous-match anchor to the START of the
985/// matched region. The default (all `false`) preserves byte-exact
986/// comparison and match-END anchor advance.
987///
988/// `SearchFlags` is structurally parallel to `StringFlags`: when one
989/// struct grows a field, the other gains the same field in lockstep
990/// so that [`SearchFlags::to_string_flags`] can keep handing off to
991/// `compare_string_with_flags` without a generic refactor. The
992/// search-only `start_anchor` field has no analog in `string` rules.
993///
994/// **`/c` vs `/C` are asymmetric** in the same way as [`StringFlags`]:
995/// the pattern character controls fold direction. See [`StringFlags`]
996/// and GOTCHAS S6.5 for the rationale.
997///
998/// # Examples
999///
1000/// ```
1001/// use libmagic_rs::parser::ast::SearchFlags;
1002///
1003/// let plain = SearchFlags::default();
1004/// assert!(!plain.start_anchor);
1005/// assert!(plain.is_empty());
1006/// assert!(!plain.needs_byte_compare());
1007///
1008/// let start = SearchFlags::default().with_start_anchor(true);
1009/// assert!(start.start_anchor);
1010/// assert!(!start.is_empty());
1011/// // /s is anchor-only -- does not force the byte-compare slow path.
1012/// assert!(!start.needs_byte_compare());
1013///
1014/// let case = SearchFlags::default().with_ignore_lowercase(true);
1015/// assert!(case.needs_byte_compare());
1016/// ```
1017#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1018// libmagic's contract is naturally a bitfield: each flag is a distinct
1019// magic(5) letter with its own STRING_*/SEARCH_* constant in libmagic
1020// src/file.h. Flags compose freely (search/cs is /c plus /s; search/sWcT
1021// sets four). Folding pairs into enums is possible but would obscure
1022// the libmagic mapping and produce verbose match arms in every consumer.
1023// The bool-per-flag layout mirrors `StringFlags` and `RegexFlags` and the
1024// libmagic source -- the clippy lint is overruled by the design.
1025#[allow(clippy::struct_excessive_bools)]
1026#[non_exhaustive]
1027pub struct SearchFlags {
1028    /// `/W` -- `STRING_COMPACT_WHITESPACE`. Pattern whitespace requires at
1029    /// least one whitespace byte in the file, then any further whitespace
1030    /// in the file is consumed greedily.
1031    pub compact_whitespace: bool,
1032    /// `/w` -- `STRING_COMPACT_OPTIONAL_WHITESPACE`. Pattern whitespace
1033    /// matches zero or more whitespace bytes in the file.
1034    pub compact_optional_whitespace: bool,
1035    /// `/c` -- `STRING_IGNORE_LOWERCASE`. When the pattern char is
1036    /// lowercase, the file byte is `to_ascii_lowercase`'d before
1037    /// comparison. Uppercase pattern chars are compared literally.
1038    pub ignore_lowercase: bool,
1039    /// `/C` -- `STRING_IGNORE_UPPERCASE`. When the pattern char is
1040    /// uppercase, the file byte is `to_ascii_uppercase`'d before
1041    /// comparison. Lowercase pattern chars are compared literally.
1042    pub ignore_uppercase: bool,
1043    /// `/t` -- `STRING_TEXTTEST`. Pass-selection hint: makes the entry a
1044    /// text-pass entry and skips it on a binary buffer when `/b` is not
1045    /// also set (GOTCHAS S13.7); no comparison effect.
1046    pub text_test: bool,
1047    /// `/T` -- `STRING_TRIM`. Trim leading and trailing ASCII whitespace
1048    /// from the pattern before comparison.
1049    pub trim: bool,
1050    /// `/b` -- `STRING_BINTEST`. Pass-selection hint: makes the entry a
1051    /// binary-pass entry and skips it on a text buffer when `/t` is not
1052    /// also set; `search/bt` runs in both passes (GOTCHAS S13.7); no
1053    /// comparison effect.
1054    pub bin_test: bool,
1055    /// `/f` -- `STRING_FULL_WORD`. Post-match check that the byte after
1056    /// the matched region is either end-of-buffer or a non-word
1057    /// character (ASCII alphanumeric or `_`).
1058    pub full_word: bool,
1059    /// `/s` -- magic(5) "search-start" flag. When `true`, the GNU `file`
1060    /// previous-match anchor advance lands on the match-START index
1061    /// rather than match-END (the default). Mirrors libmagic's
1062    /// `FILE_SEARCH` anchor handling in `src/softmagic.c::moffset`. The
1063    /// dispatch happens in
1064    /// `src/evaluator/types/search.rs::search_bytes_consumed`.
1065    pub start_anchor: bool,
1066}
1067
1068impl SearchFlags {
1069    /// Returns `true` when every flag is `false` (default-constructed).
1070    #[must_use]
1071    pub const fn is_empty(self) -> bool {
1072        !self.compact_whitespace
1073            && !self.compact_optional_whitespace
1074            && !self.ignore_lowercase
1075            && !self.ignore_uppercase
1076            && !self.text_test
1077            && !self.trim
1078            && !self.bin_test
1079            && !self.full_word
1080            && !self.start_anchor
1081    }
1082
1083    /// Returns `true` when any flag alters the literal-pattern
1084    /// comparison, forcing the byte-walk slow path through
1085    /// `compare_string_with_flags`. The anchor-only / metadata-only
1086    /// flags (`/s`, `/t`, `/b`) do **not** trigger byte-compare;
1087    /// they preserve the `memchr::memmem::find` fast path.
1088    #[must_use]
1089    pub const fn needs_byte_compare(self) -> bool {
1090        self.compact_whitespace
1091            || self.compact_optional_whitespace
1092            || self.ignore_lowercase
1093            || self.ignore_uppercase
1094            || self.trim
1095            || self.full_word
1096    }
1097
1098    /// Project the eight shared flag fields onto a [`StringFlags`] for
1099    /// handoff to `compare_string_with_flags`. The search-only
1100    /// `start_anchor` field is dropped (it is anchor-advance policy,
1101    /// not comparison policy).
1102    ///
1103    /// # Examples
1104    ///
1105    /// ```
1106    /// use libmagic_rs::parser::ast::SearchFlags;
1107    ///
1108    /// let sf = SearchFlags::default()
1109    ///     .with_ignore_lowercase(true)
1110    ///     .with_trim(true)
1111    ///     .with_start_anchor(true);
1112    /// let projected = sf.to_string_flags();
1113    /// assert!(projected.ignore_lowercase);
1114    /// assert!(projected.trim);
1115    /// // /s has no analog in StringFlags.
1116    /// ```
1117    #[must_use]
1118    pub const fn to_string_flags(self) -> StringFlags {
1119        StringFlags {
1120            compact_whitespace: self.compact_whitespace,
1121            compact_optional_whitespace: self.compact_optional_whitespace,
1122            ignore_lowercase: self.ignore_lowercase,
1123            ignore_uppercase: self.ignore_uppercase,
1124            text_test: self.text_test,
1125            trim: self.trim,
1126            bin_test: self.bin_test,
1127            full_word: self.full_word,
1128        }
1129    }
1130
1131    /// Builder-style setter for `compact_whitespace` (`/W`).
1132    #[must_use]
1133    pub const fn with_compact_whitespace(mut self, value: bool) -> Self {
1134        self.compact_whitespace = value;
1135        self
1136    }
1137
1138    /// Builder-style setter for `compact_optional_whitespace` (`/w`).
1139    #[must_use]
1140    pub const fn with_compact_optional_whitespace(mut self, value: bool) -> Self {
1141        self.compact_optional_whitespace = value;
1142        self
1143    }
1144
1145    /// Builder-style setter for `ignore_lowercase` (`/c`).
1146    #[must_use]
1147    pub const fn with_ignore_lowercase(mut self, value: bool) -> Self {
1148        self.ignore_lowercase = value;
1149        self
1150    }
1151
1152    /// Builder-style setter for `ignore_uppercase` (`/C`).
1153    #[must_use]
1154    pub const fn with_ignore_uppercase(mut self, value: bool) -> Self {
1155        self.ignore_uppercase = value;
1156        self
1157    }
1158
1159    /// Builder-style setter for `text_test` (`/t`).
1160    #[must_use]
1161    pub const fn with_text_test(mut self, value: bool) -> Self {
1162        self.text_test = value;
1163        self
1164    }
1165
1166    /// Builder-style setter for `trim` (`/T`).
1167    #[must_use]
1168    pub const fn with_trim(mut self, value: bool) -> Self {
1169        self.trim = value;
1170        self
1171    }
1172
1173    /// Builder-style setter for `bin_test` (`/b`).
1174    #[must_use]
1175    pub const fn with_bin_test(mut self, value: bool) -> Self {
1176        self.bin_test = value;
1177        self
1178    }
1179
1180    /// Builder-style setter for `full_word` (`/f`).
1181    #[must_use]
1182    pub const fn with_full_word(mut self, value: bool) -> Self {
1183        self.full_word = value;
1184        self
1185    }
1186
1187    /// Builder-style setter for `start_anchor` (`/s`).
1188    #[must_use]
1189    pub const fn with_start_anchor(mut self, value: bool) -> Self {
1190        self.start_anchor = value;
1191        self
1192    }
1193}
1194
1195/// Scan window specifier for a [`TypeKind::Regex`] rule.
1196///
1197/// Encodes the three mutually-exclusive scan modes in a single enum so
1198/// that the "byte count" and "line count" cases cannot be confused. The
1199/// `regex/l` shorthand (line mode with no explicit count) is represented
1200/// explicitly as [`RegexCount::Lines(None)`](RegexCount::Lines), which
1201/// is behaviorally equivalent to [`RegexCount::Default`] -- both walk
1202/// the full 8192-byte capped window -- but preserves the magic-file
1203/// surface syntax of the original rule. The 8192-byte hard cap
1204/// (matching GNU `file`'s `FILE_REGEX_MAX`) is applied by the evaluator
1205/// on every variant.
1206///
1207/// # Examples
1208///
1209/// ```
1210/// use libmagic_rs::parser::ast::RegexCount;
1211/// use std::num::NonZeroU32;
1212///
1213/// // Plain `regex` (no suffix): default 8192-byte window.
1214/// assert_eq!(RegexCount::default(), RegexCount::Default);
1215///
1216/// // `regex/100`: scan at most 100 bytes.
1217/// let hundred_bytes = RegexCount::Bytes(NonZeroU32::new(100).unwrap());
1218///
1219/// // `regex/1l`: scan the first line.
1220/// let one_line = RegexCount::Lines(NonZeroU32::new(1));
1221///
1222/// // `regex/l`: line-mode with no explicit count (walks terminators
1223/// // to the end of the 8192-byte capped window).
1224/// let unbounded_lines = RegexCount::Lines(None);
1225/// ```
1226#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1227pub enum RegexCount {
1228    /// No scan bound (plain `regex` with no suffix). Scans the default
1229    /// 8192-byte window from the rule's offset.
1230    #[default]
1231    Default,
1232    /// Byte-bounded scan (`regex/N` with no `/l` flag). The window is
1233    /// `min(n, 8192, remaining_buffer)` bytes long. `NonZeroU32` makes
1234    /// a zero-byte scan unrepresentable.
1235    Bytes(NonZeroU32),
1236    /// Line-bounded scan (`regex/Nl` or `regex/l`). The window walks
1237    /// LF / CRLF / bare CR line terminators from the offset. With
1238    /// `Some(n)`, the walk stops after the Nth terminator (inclusive).
1239    /// With `None` (the `regex/l` shorthand), the walk continues to
1240    /// the end of the 8192-byte capped window. Either way the
1241    /// effective byte window is capped at 8192.
1242    Lines(Option<NonZeroU32>),
1243}
1244
1245impl TypeKind {
1246    /// Returns the bit width of integer types, or `None` for non-integer types (e.g., String).
1247    ///
1248    /// # Examples
1249    ///
1250    /// ```
1251    /// use libmagic_rs::parser::ast::{Endianness, StringFlags, TypeKind};
1252    ///
1253    /// assert_eq!(TypeKind::Byte { signed: false }.bit_width(), Some(8));
1254    /// assert_eq!(TypeKind::Short { endian: Endianness::Native, signed: true }.bit_width(), Some(16));
1255    /// assert_eq!(TypeKind::Long { endian: Endianness::Native, signed: true }.bit_width(), Some(32));
1256    /// assert_eq!(TypeKind::Quad { endian: Endianness::Native, signed: true }.bit_width(), Some(64));
1257    /// assert_eq!(TypeKind::Float { endian: Endianness::Native }.bit_width(), Some(32));
1258    /// assert_eq!(TypeKind::Double { endian: Endianness::Native }.bit_width(), Some(64));
1259    /// assert_eq!(TypeKind::String { max_length: None, flags: StringFlags::default() }.bit_width(), None);
1260    /// ```
1261    #[must_use]
1262    pub const fn bit_width(&self) -> Option<u32> {
1263        match self {
1264            Self::Byte { .. } => Some(8),
1265            Self::Short { .. } => Some(16),
1266            Self::Long { .. } | Self::Id3 { .. } | Self::Float { .. } | Self::Date { .. } => {
1267                Some(32)
1268            }
1269            Self::Quad { .. } | Self::Double { .. } | Self::QDate { .. } => Some(64),
1270            Self::String { .. }
1271            | Self::String16 { .. }
1272            | Self::PString { .. }
1273            | Self::Regex { .. }
1274            | Self::Search { .. }
1275            | Self::Meta(_) => None,
1276        }
1277    }
1278}
1279
1280/// Comparison and bitwise operators
1281#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1282#[non_exhaustive]
1283pub enum Operator {
1284    /// Equality comparison (`=` or `==`)
1285    ///
1286    /// # Examples
1287    ///
1288    /// ```
1289    /// use libmagic_rs::parser::ast::Operator;
1290    ///
1291    /// let op = Operator::Equal;
1292    /// assert_eq!(op, Operator::Equal);
1293    /// ```
1294    Equal,
1295    /// Inequality comparison (`!=` or `<>`)
1296    ///
1297    /// # Examples
1298    ///
1299    /// ```
1300    /// use libmagic_rs::parser::ast::Operator;
1301    ///
1302    /// let op = Operator::NotEqual;
1303    /// assert_eq!(op, Operator::NotEqual);
1304    /// ```
1305    NotEqual,
1306    /// Less-than comparison (`<`)
1307    ///
1308    /// # Examples
1309    ///
1310    /// ```
1311    /// use libmagic_rs::parser::ast::Operator;
1312    ///
1313    /// let op = Operator::LessThan;
1314    /// assert_eq!(op, Operator::LessThan);
1315    /// ```
1316    LessThan,
1317    /// Greater-than comparison (`>`)
1318    ///
1319    /// # Examples
1320    ///
1321    /// ```
1322    /// use libmagic_rs::parser::ast::Operator;
1323    ///
1324    /// let op = Operator::GreaterThan;
1325    /// assert_eq!(op, Operator::GreaterThan);
1326    /// ```
1327    GreaterThan,
1328    /// Less-than-or-equal comparison (`<=`)
1329    ///
1330    /// # Examples
1331    ///
1332    /// ```
1333    /// use libmagic_rs::parser::ast::Operator;
1334    ///
1335    /// let op = Operator::LessEqual;
1336    /// assert_eq!(op, Operator::LessEqual);
1337    /// ```
1338    LessEqual,
1339    /// Greater-than-or-equal comparison (`>=`)
1340    ///
1341    /// # Examples
1342    ///
1343    /// ```
1344    /// use libmagic_rs::parser::ast::Operator;
1345    ///
1346    /// let op = Operator::GreaterEqual;
1347    /// assert_eq!(op, Operator::GreaterEqual);
1348    /// ```
1349    GreaterEqual,
1350    /// Bitwise AND operation without mask (`&`)
1351    ///
1352    /// # Examples
1353    ///
1354    /// ```
1355    /// use libmagic_rs::parser::ast::Operator;
1356    ///
1357    /// let op = Operator::BitwiseAnd;
1358    /// assert_eq!(op, Operator::BitwiseAnd);
1359    /// ```
1360    BitwiseAnd,
1361    /// Bitwise AND operation with mask value (`&` with a mask operand)
1362    ///
1363    /// # Examples
1364    ///
1365    /// ```
1366    /// use libmagic_rs::parser::ast::Operator;
1367    ///
1368    /// let op = Operator::BitwiseAndMask(0xFF00);
1369    /// assert_eq!(op, Operator::BitwiseAndMask(0xFF00));
1370    /// ```
1371    BitwiseAndMask(u64),
1372    /// Bitwise XOR operation (`^`)
1373    ///
1374    /// # Examples
1375    ///
1376    /// ```
1377    /// use libmagic_rs::parser::ast::Operator;
1378    ///
1379    /// let op = Operator::BitwiseXor;
1380    /// assert_eq!(op, Operator::BitwiseXor);
1381    /// ```
1382    BitwiseXor,
1383    /// Bitwise NOT/complement operation (`~`)
1384    ///
1385    /// # Examples
1386    ///
1387    /// ```
1388    /// use libmagic_rs::parser::ast::Operator;
1389    ///
1390    /// let op = Operator::BitwiseNot;
1391    /// assert_eq!(op, Operator::BitwiseNot);
1392    /// ```
1393    BitwiseNot,
1394    /// Match any value; condition always succeeds (`x`)
1395    ///
1396    /// # Examples
1397    ///
1398    /// ```
1399    /// use libmagic_rs::parser::ast::Operator;
1400    ///
1401    /// let op = Operator::AnyValue;
1402    /// assert_eq!(op, Operator::AnyValue);
1403    /// ```
1404    AnyValue,
1405}
1406
1407/// Value types for rule matching
1408#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1409#[non_exhaustive]
1410pub enum Value {
1411    /// Unsigned integer value
1412    ///
1413    /// # Examples
1414    ///
1415    /// ```
1416    /// use libmagic_rs::parser::ast::Value;
1417    ///
1418    /// let val = Value::Uint(0xDEAD_BEEF);
1419    /// assert_eq!(val, Value::Uint(0xDEAD_BEEF));
1420    /// ```
1421    Uint(u64),
1422    /// Signed integer value
1423    ///
1424    /// # Examples
1425    ///
1426    /// ```
1427    /// use libmagic_rs::parser::ast::Value;
1428    ///
1429    /// let val = Value::Int(-42);
1430    /// assert_eq!(val, Value::Int(-42));
1431    /// ```
1432    Int(i64),
1433    /// Floating-point value (used for `float` and `double` types)
1434    ///
1435    /// # Examples
1436    ///
1437    /// ```
1438    /// use libmagic_rs::parser::ast::Value;
1439    ///
1440    /// let val = Value::Float(3.14);
1441    /// assert_eq!(val, Value::Float(3.14));
1442    /// ```
1443    Float(f64),
1444    /// Byte sequence
1445    ///
1446    /// # Examples
1447    ///
1448    /// ```
1449    /// use libmagic_rs::parser::ast::Value;
1450    ///
1451    /// let val = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
1452    /// assert_eq!(val, Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]));
1453    /// ```
1454    Bytes(Vec<u8>),
1455    /// String value
1456    ///
1457    /// # Examples
1458    ///
1459    /// ```
1460    /// use libmagic_rs::parser::ast::Value;
1461    ///
1462    /// let val = Value::String("MZ".to_string());
1463    /// assert_eq!(val, Value::String("MZ".to_string()));
1464    /// ```
1465    String(String),
1466}
1467
1468/// Endianness specification for multi-byte values
1469#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1470pub enum Endianness {
1471    /// Little-endian byte order (least significant byte first)
1472    ///
1473    /// # Examples
1474    ///
1475    /// ```
1476    /// use libmagic_rs::parser::ast::Endianness;
1477    ///
1478    /// let e = Endianness::Little;
1479    /// assert_eq!(e, Endianness::Little);
1480    /// ```
1481    Little,
1482    /// Big-endian byte order (most significant byte first)
1483    ///
1484    /// # Examples
1485    ///
1486    /// ```
1487    /// use libmagic_rs::parser::ast::Endianness;
1488    ///
1489    /// let e = Endianness::Big;
1490    /// assert_eq!(e, Endianness::Big);
1491    /// ```
1492    Big,
1493    /// Native system byte order (matches target architecture)
1494    ///
1495    /// # Examples
1496    ///
1497    /// ```
1498    /// use libmagic_rs::parser::ast::Endianness;
1499    ///
1500    /// let e = Endianness::Native;
1501    /// assert_eq!(e, Endianness::Native);
1502    /// ```
1503    Native,
1504}
1505
1506/// Strength modifier for magic rules
1507///
1508/// Strength modifiers adjust the default strength calculation for a rule.
1509/// They are specified using the `!:strength` directive in magic files.
1510///
1511/// # Examples
1512///
1513/// ```
1514/// use libmagic_rs::parser::ast::StrengthModifier;
1515///
1516/// let add = StrengthModifier::Add(10);      // !:strength +10
1517/// let sub = StrengthModifier::Subtract(5);  // !:strength -5
1518/// let mul = StrengthModifier::Multiply(2);  // !:strength *2
1519/// let div = StrengthModifier::Divide(2);    // !:strength /2
1520/// let set = StrengthModifier::Set(50);      // !:strength =50
1521/// ```
1522#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1523pub enum StrengthModifier {
1524    /// Add to the default strength: `!:strength +N`
1525    Add(i32),
1526    /// Subtract from the default strength: `!:strength -N`
1527    Subtract(i32),
1528    /// Multiply the default strength: `!:strength *N`
1529    Multiply(i32),
1530    /// Divide the default strength: `!:strength /N`
1531    Divide(i32),
1532    /// Set strength to an absolute value: `!:strength =N` or `!:strength N`
1533    Set(i32),
1534}
1535
1536/// Arithmetic operation applied to a value read from the file *before* the
1537/// rule's comparison operator is evaluated.
1538///
1539/// magic(5) supports `+`, `-`, `*`, `/`, `%`, `|`, and `^` between the type
1540/// keyword and the comparison value (e.g., `lelong+1 x volume %d` reads a
1541/// long, adds 1, and formats the transformed value into the message).
1542/// Bitwise AND (`&MASK`) is *not* part of this enum because it is already
1543/// represented at the operator level via [`Operator::BitwiseAndMask`].
1544///
1545/// The operand is signed (`i64`) so that subtraction and negative multipliers
1546/// round-trip cleanly. Bitwise ops reinterpret the operand as a `u64` bit
1547/// pattern at evaluation time, matching libmagic's `apprentice.c::mconvert`.
1548#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1549#[non_exhaustive]
1550pub enum ValueTransformOp {
1551    /// Addition (`type+N`).
1552    Add,
1553    /// Subtraction (`type-N`).
1554    Sub,
1555    /// Multiplication (`type*N`).
1556    Mul,
1557    /// Truncating integer division (`type/N`). Division by zero is rejected
1558    /// at evaluation time.
1559    Div,
1560    /// Remainder (`type%N`). Modulo by zero is rejected at evaluation time.
1561    Mod,
1562    /// Bitwise AND (`type&N`).
1563    ///
1564    /// magic(5) `&MASK` was historically encoded at the operator level
1565    /// via [`Operator::BitwiseAndMask`] (which combines mask+equal in
1566    /// one step). That encoding cannot represent rules like `lelong&0xff
1567    /// x %d` (mask + any-value, with the masked value used in format
1568    /// substitution). The parser promotes `&MASK` to this `BitAnd`
1569    /// transform when followed by another operator (`x`, `>`, `!=`, ...)
1570    /// so the read value is masked before comparison and before printf
1571    /// substitution. The legacy `&MASK VALUE` form (mask + implicit
1572    /// equal) keeps using `Operator::BitwiseAndMask` for backwards
1573    /// compatibility.
1574    BitAnd,
1575    /// Bitwise OR (`type|N`).
1576    Or,
1577    /// Bitwise XOR (`type^N`).
1578    Xor,
1579}
1580
1581/// A pre-comparison value transform: `(op, operand)`.
1582///
1583/// Applied to the value read from the file before the rule's comparison
1584/// operator runs. See [`ValueTransformOp`] for the supported operations.
1585///
1586/// # Examples
1587///
1588/// ```
1589/// use libmagic_rs::parser::ast::{ValueTransform, ValueTransformOp};
1590///
1591/// // `lelong+1` -> add 1 to the read value
1592/// let t = ValueTransform::new(ValueTransformOp::Add, 1);
1593/// assert_eq!(t.op, ValueTransformOp::Add);
1594/// assert_eq!(t.operand, 1);
1595/// ```
1596#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1597#[non_exhaustive]
1598pub struct ValueTransform {
1599    /// Operation to apply.
1600    pub op: ValueTransformOp,
1601    /// Operand to combine with the read value.
1602    pub operand: i64,
1603}
1604
1605impl ValueTransform {
1606    /// Construct a new `ValueTransform` from an op and an operand.
1607    #[must_use]
1608    pub const fn new(op: ValueTransformOp, operand: i64) -> Self {
1609        Self { op, operand }
1610    }
1611}
1612
1613/// Magic rule representation in the AST
1614#[derive(Debug, Clone, Serialize, Deserialize)]
1615#[non_exhaustive]
1616pub struct MagicRule {
1617    /// Offset specification for where to read data
1618    pub offset: OffsetSpec,
1619    /// Type of data to read and interpret
1620    pub typ: TypeKind,
1621    /// Comparison operator to apply
1622    pub op: Operator,
1623    /// Expected value for comparison
1624    pub value: Value,
1625    /// Human-readable message for this rule
1626    pub message: String,
1627    /// Child rules that are evaluated if this rule matches
1628    pub children: Vec<MagicRule>,
1629    /// Indentation level for hierarchical rules
1630    pub level: u32,
1631    /// Optional strength modifier from `!:strength` directive
1632    pub strength_modifier: Option<StrengthModifier>,
1633    /// Optional pre-comparison value transform from a magic-file
1634    /// type-suffix like `lelong+1` or `ulequad/1073741824`. When set,
1635    /// the read value is transformed *before* `op` is evaluated and
1636    /// before the message's `%`-format substitution, so format
1637    /// specifiers see the post-transform number.
1638    ///
1639    /// `#[serde(default)]` keeps existing serialized AST snapshots
1640    /// (which never had this field) round-tripping correctly: missing
1641    /// fields deserialize to `None`, which means "no transform" --
1642    /// the historical behavior.
1643    #[serde(default)]
1644    pub value_transform: Option<ValueTransform>,
1645}
1646
1647/// Validation errors returned by [`MagicRule::validate`].
1648#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1649#[non_exhaustive]
1650pub enum MagicRuleValidationError {
1651    /// Rule message is empty. Messages are user-facing and required
1652    /// for meaningful output.
1653    #[error("rule message must not be empty")]
1654    EmptyMessage,
1655
1656    /// The child rule at `child_index` has `level <= self.level`,
1657    /// violating the "children must nest deeper than the parent"
1658    /// invariant of the hierarchical indentation-based DSL.
1659    #[error(
1660        "child rule at index {child_index} has level {child_level}, \
1661         must be greater than parent level {parent_level}"
1662    )]
1663    InvalidChildLevel {
1664        /// Index of the offending child in `self.children`.
1665        child_index: usize,
1666        /// Level of the child rule.
1667        child_level: u32,
1668        /// Level of the parent rule.
1669        parent_level: u32,
1670    },
1671
1672    /// Rule `level` exceeds the maximum supported depth. The limit is a
1673    /// hardening mechanism against stack overflow during deep recursion;
1674    /// libmagic files in the wild rarely go beyond 10 levels.
1675    #[error("rule level {level} exceeds maximum supported depth {max}")]
1676    LevelTooDeep {
1677        /// The invalid level value.
1678        level: u32,
1679        /// The maximum allowed depth.
1680        max: u32,
1681    },
1682}
1683
1684impl MagicRule {
1685    /// Hard structural ceiling on rule `level`.
1686    ///
1687    /// This is a conservative upper bound enforced by
1688    /// [`MagicRule::validate`] to keep the AST shape sane: real
1689    /// magic files in the wild rarely exceed ~10 levels of nesting,
1690    /// so rejecting rules with `level > 1000` catches obviously
1691    /// pathological input at construction time without constraining
1692    /// any legitimate rule.
1693    ///
1694    /// This ceiling is **independent of** the evaluator's
1695    /// `EvaluationConfig::max_recursion_depth` (default 20), which
1696    /// is the *runtime* recursion guard applied during rule
1697    /// evaluation. The evaluator limit is the first one that fires
1698    /// in practice -- a rule tree with 50 levels passes this
1699    /// structural check but is aborted by the evaluator long before
1700    /// reaching `MAX_LEVEL`. The two limits serve different purposes:
1701    /// `MAX_LEVEL` is an AST-shape sanity check, and
1702    /// `max_recursion_depth` is a per-evaluation resource bound.
1703    pub const MAX_LEVEL: u32 = 1000;
1704
1705    /// Construct a top-level rule with no children and no strength
1706    /// modifier.
1707    ///
1708    /// This is the most common constructor for programmatically building
1709    /// rules outside the parser. To add children, mutate
1710    /// [`MagicRule::children`] directly, or use [`MagicRule::with_children`].
1711    /// To set a strength modifier, use
1712    /// [`MagicRule::with_strength_modifier`].
1713    ///
1714    /// # Examples
1715    ///
1716    /// ```rust
1717    /// use libmagic_rs::{MagicRule, OffsetSpec, Operator, TypeKind, Value};
1718    ///
1719    /// let rule = MagicRule::new(
1720    ///     OffsetSpec::Absolute(0),
1721    ///     TypeKind::Byte { signed: false },
1722    ///     Operator::Equal,
1723    ///     Value::Uint(0x7f),
1724    ///     "ELF magic byte".to_string(),
1725    /// );
1726    /// assert_eq!(rule.level, 0);
1727    /// assert!(rule.children.is_empty());
1728    /// assert!(rule.validate().is_ok());
1729    /// ```
1730    #[must_use]
1731    pub fn new(
1732        offset: OffsetSpec,
1733        typ: TypeKind,
1734        op: Operator,
1735        value: Value,
1736        message: String,
1737    ) -> Self {
1738        Self {
1739            offset,
1740            typ,
1741            op,
1742            value,
1743            message,
1744            children: vec![],
1745            level: 0,
1746            strength_modifier: None,
1747            value_transform: None,
1748        }
1749    }
1750
1751    /// Replace `self.children` with the given children and return the
1752    /// modified rule. Builder-style for chaining.
1753    #[must_use]
1754    pub fn with_children(mut self, children: Vec<MagicRule>) -> Self {
1755        self.children = children;
1756        self
1757    }
1758
1759    /// Set `self.strength_modifier` to the given value and return the
1760    /// modified rule. Builder-style for chaining.
1761    #[must_use]
1762    pub const fn with_strength_modifier(mut self, modifier: StrengthModifier) -> Self {
1763        self.strength_modifier = Some(modifier);
1764        self
1765    }
1766
1767    /// Set `self.level` to the given value and return the modified rule.
1768    /// Builder-style for chaining; typically used only when constructing
1769    /// child rules programmatically.
1770    #[must_use]
1771    pub const fn with_level(mut self, level: u32) -> Self {
1772        self.level = level;
1773        self
1774    }
1775
1776    /// Validate structural invariants of the rule.
1777    ///
1778    /// This checks invariants that the parser enforces automatically but
1779    /// that programmatic constructors (especially via serde deserialize)
1780    /// can violate:
1781    ///
1782    /// * Message must not be empty.
1783    /// * `level` must not exceed [`Self::MAX_LEVEL`].
1784    /// * Every child's `level` must be strictly greater than
1785    ///   `self.level`, and each child must recursively validate.
1786    ///
1787    /// This does *not* validate that `value` is shape-compatible with
1788    /// `typ` (e.g., a `Value::Uint` against a `TypeKind::String`); such
1789    /// mismatches are coerced or rejected by the evaluator at match time.
1790    ///
1791    /// # Errors
1792    ///
1793    /// Returns [`MagicRuleValidationError`] describing the first
1794    /// invariant violation encountered.
1795    ///
1796    /// # Examples
1797    ///
1798    /// ```rust
1799    /// use libmagic_rs::{MagicRule, OffsetSpec, Operator, TypeKind, Value};
1800    ///
1801    /// let rule = MagicRule::new(
1802    ///     OffsetSpec::Absolute(0),
1803    ///     TypeKind::Byte { signed: false },
1804    ///     Operator::Equal,
1805    ///     Value::Uint(0),
1806    ///     "zero byte".to_string(),
1807    /// );
1808    /// assert!(rule.validate().is_ok());
1809    /// ```
1810    pub fn validate(&self) -> Result<(), MagicRuleValidationError> {
1811        if self.message.is_empty() {
1812            return Err(MagicRuleValidationError::EmptyMessage);
1813        }
1814        if self.level > Self::MAX_LEVEL {
1815            return Err(MagicRuleValidationError::LevelTooDeep {
1816                level: self.level,
1817                max: Self::MAX_LEVEL,
1818            });
1819        }
1820        for (child_index, child) in self.children.iter().enumerate() {
1821            if child.level <= self.level {
1822                return Err(MagicRuleValidationError::InvalidChildLevel {
1823                    child_index,
1824                    child_level: child.level,
1825                    parent_level: self.level,
1826                });
1827            }
1828            child.validate()?;
1829        }
1830        Ok(())
1831    }
1832}
1833
1834#[cfg(test)]
1835mod tests {
1836    // Restriction lints without an allow-*-in-tests config option;
1837    // non-ASCII test data exercises string value handling.
1838    #![allow(clippy::non_ascii_literal)]
1839
1840    use super::*;
1841
1842    #[test]
1843    fn test_magic_rule_new_defaults() {
1844        let rule = MagicRule::new(
1845            OffsetSpec::Absolute(0),
1846            TypeKind::Byte { signed: false },
1847            Operator::Equal,
1848            Value::Uint(0x7f),
1849            "ELF".to_string(),
1850        );
1851        assert_eq!(rule.level, 0);
1852        assert!(rule.children.is_empty());
1853        assert!(rule.strength_modifier.is_none());
1854        assert!(rule.validate().is_ok());
1855    }
1856
1857    #[test]
1858    fn test_magic_rule_builder_chain() {
1859        let child = MagicRule::new(
1860            OffsetSpec::Absolute(4),
1861            TypeKind::Byte { signed: false },
1862            Operator::Equal,
1863            Value::Uint(2),
1864            "64-bit".to_string(),
1865        )
1866        .with_level(1);
1867        let parent = MagicRule::new(
1868            OffsetSpec::Absolute(0),
1869            TypeKind::Byte { signed: false },
1870            Operator::Equal,
1871            Value::Uint(0x7f),
1872            "ELF".to_string(),
1873        )
1874        .with_children(vec![child])
1875        .with_strength_modifier(StrengthModifier::Add(10));
1876        assert_eq!(parent.children.len(), 1);
1877        assert_eq!(parent.strength_modifier, Some(StrengthModifier::Add(10)));
1878        assert!(parent.validate().is_ok());
1879    }
1880
1881    #[test]
1882    fn test_magic_rule_validate_empty_message_rejected() {
1883        let rule = MagicRule::new(
1884            OffsetSpec::Absolute(0),
1885            TypeKind::Byte { signed: false },
1886            Operator::Equal,
1887            Value::Uint(0),
1888            String::new(),
1889        );
1890        assert_eq!(rule.validate(), Err(MagicRuleValidationError::EmptyMessage));
1891    }
1892
1893    #[test]
1894    fn test_magic_rule_validate_child_level_must_be_deeper() {
1895        let child_same_level = MagicRule::new(
1896            OffsetSpec::Absolute(4),
1897            TypeKind::Byte { signed: false },
1898            Operator::Equal,
1899            Value::Uint(2),
1900            "child".to_string(),
1901        ); // level = 0, same as parent
1902        let parent = MagicRule::new(
1903            OffsetSpec::Absolute(0),
1904            TypeKind::Byte { signed: false },
1905            Operator::Equal,
1906            Value::Uint(0x7f),
1907            "parent".to_string(),
1908        )
1909        .with_children(vec![child_same_level]);
1910        assert_eq!(
1911            parent.validate(),
1912            Err(MagicRuleValidationError::InvalidChildLevel {
1913                child_index: 0,
1914                child_level: 0,
1915                parent_level: 0,
1916            })
1917        );
1918    }
1919
1920    #[test]
1921    fn test_magic_rule_validate_level_too_deep() {
1922        let rule = MagicRule::new(
1923            OffsetSpec::Absolute(0),
1924            TypeKind::Byte { signed: false },
1925            Operator::Equal,
1926            Value::Uint(0),
1927            "deep".to_string(),
1928        )
1929        .with_level(MagicRule::MAX_LEVEL + 1);
1930        assert_eq!(
1931            rule.validate(),
1932            Err(MagicRuleValidationError::LevelTooDeep {
1933                level: MagicRule::MAX_LEVEL + 1,
1934                max: MagicRule::MAX_LEVEL,
1935            })
1936        );
1937    }
1938
1939    #[test]
1940    fn test_offset_spec_absolute() {
1941        let offset = OffsetSpec::Absolute(42);
1942        assert_eq!(offset, OffsetSpec::Absolute(42));
1943
1944        // Test negative offset
1945        let negative = OffsetSpec::Absolute(-10);
1946        assert_eq!(negative, OffsetSpec::Absolute(-10));
1947    }
1948
1949    #[test]
1950    fn test_offset_spec_indirect() {
1951        let indirect = OffsetSpec::Indirect {
1952            base_offset: 0x20,
1953            base_relative: false,
1954            pointer_type: TypeKind::Long {
1955                endian: Endianness::Little,
1956                signed: false,
1957            },
1958            adjustment: 4,
1959            adjustment_op: IndirectAdjustmentOp::Add,
1960            result_relative: false,
1961            endian: Endianness::Little,
1962        };
1963
1964        match indirect {
1965            OffsetSpec::Indirect {
1966                base_offset,
1967                adjustment,
1968                ..
1969            } => {
1970                assert_eq!(base_offset, 0x20);
1971                assert_eq!(adjustment, 4);
1972            }
1973            _ => panic!("Expected Indirect variant"),
1974        }
1975    }
1976
1977    #[test]
1978    fn test_offset_spec_relative() {
1979        let relative = OffsetSpec::Relative(8);
1980        assert_eq!(relative, OffsetSpec::Relative(8));
1981
1982        // Test negative relative offset
1983        let negative_relative = OffsetSpec::Relative(-4);
1984        assert_eq!(negative_relative, OffsetSpec::Relative(-4));
1985    }
1986
1987    #[test]
1988    fn test_offset_spec_from_end() {
1989        let from_end = OffsetSpec::FromEnd(-16);
1990        assert_eq!(from_end, OffsetSpec::FromEnd(-16));
1991
1992        // Test positive from_end (though unusual)
1993        let positive_from_end = OffsetSpec::FromEnd(8);
1994        assert_eq!(positive_from_end, OffsetSpec::FromEnd(8));
1995    }
1996
1997    #[test]
1998    fn test_offset_spec_debug() {
1999        let offset = OffsetSpec::Absolute(100);
2000        let debug_str = format!("{offset:?}");
2001        assert!(debug_str.contains("Absolute"));
2002        assert!(debug_str.contains("100"));
2003    }
2004
2005    #[test]
2006    fn test_offset_spec_clone() {
2007        let original = OffsetSpec::Indirect {
2008            base_offset: 0x10,
2009            base_relative: false,
2010            pointer_type: TypeKind::Short {
2011                endian: Endianness::Big,
2012                signed: true,
2013            },
2014            adjustment: -2,
2015            adjustment_op: IndirectAdjustmentOp::Add,
2016            result_relative: false,
2017            endian: Endianness::Big,
2018        };
2019
2020        let cloned = original.clone();
2021        assert_eq!(original, cloned);
2022    }
2023
2024    #[test]
2025    fn test_offset_spec_serialization() {
2026        let offset = OffsetSpec::Absolute(42);
2027
2028        // Test JSON serialization
2029        let json = serde_json::to_string(&offset).expect("Failed to serialize");
2030        let deserialized: OffsetSpec = serde_json::from_str(&json).expect("Failed to deserialize");
2031
2032        assert_eq!(offset, deserialized);
2033    }
2034
2035    #[test]
2036    fn test_offset_spec_indirect_serialization() {
2037        let indirect = OffsetSpec::Indirect {
2038            base_offset: 0x100,
2039            base_relative: false,
2040            pointer_type: TypeKind::Long {
2041                endian: Endianness::Native,
2042                signed: false,
2043            },
2044            adjustment: 12,
2045            adjustment_op: IndirectAdjustmentOp::Add,
2046            result_relative: false,
2047            endian: Endianness::Native,
2048        };
2049
2050        // Test JSON serialization for complex variant
2051        let json = serde_json::to_string(&indirect).expect("Failed to serialize");
2052        let deserialized: OffsetSpec = serde_json::from_str(&json).expect("Failed to deserialize");
2053
2054        assert_eq!(indirect, deserialized);
2055    }
2056
2057    #[test]
2058    fn test_all_offset_spec_variants() {
2059        let variants = [
2060            OffsetSpec::Absolute(0),
2061            OffsetSpec::Absolute(-100),
2062            OffsetSpec::Indirect {
2063                base_offset: 0x20,
2064                base_relative: false,
2065                pointer_type: TypeKind::Byte { signed: true },
2066                adjustment: 0,
2067                adjustment_op: IndirectAdjustmentOp::Add,
2068                result_relative: false,
2069                endian: Endianness::Little,
2070            },
2071            OffsetSpec::Relative(50),
2072            OffsetSpec::Relative(-25),
2073            OffsetSpec::FromEnd(-8),
2074            OffsetSpec::FromEnd(4),
2075        ];
2076
2077        // Test that all variants can be created and are distinct
2078        for (i, variant) in variants.iter().enumerate() {
2079            for (j, other) in variants.iter().enumerate() {
2080                if i != j {
2081                    assert_ne!(
2082                        variant, other,
2083                        "Variants at indices {i} and {j} should be different"
2084                    );
2085                }
2086            }
2087        }
2088    }
2089
2090    #[test]
2091    fn test_endianness_variants() {
2092        let endianness_values = vec![Endianness::Little, Endianness::Big, Endianness::Native];
2093
2094        for endian in endianness_values {
2095            let indirect = OffsetSpec::Indirect {
2096                base_offset: 0,
2097                base_relative: false,
2098                pointer_type: TypeKind::Long {
2099                    endian,
2100                    signed: false,
2101                },
2102                adjustment: 0,
2103                adjustment_op: IndirectAdjustmentOp::Add,
2104                result_relative: false,
2105                endian,
2106            };
2107
2108            // Verify the endianness is preserved
2109            match indirect {
2110                OffsetSpec::Indirect {
2111                    endian: actual_endian,
2112                    ..
2113                } => {
2114                    assert_eq!(endian, actual_endian);
2115                }
2116                _ => panic!("Expected Indirect variant"),
2117            }
2118        }
2119    }
2120
2121    // Value enum tests
2122    #[test]
2123    fn test_value_uint() {
2124        let value = Value::Uint(42);
2125        assert_eq!(value, Value::Uint(42));
2126
2127        // Test large values
2128        let large_value = Value::Uint(u64::MAX);
2129        assert_eq!(large_value, Value::Uint(u64::MAX));
2130    }
2131
2132    #[test]
2133    fn test_value_int() {
2134        let positive = Value::Int(100);
2135        assert_eq!(positive, Value::Int(100));
2136
2137        let negative = Value::Int(-50);
2138        assert_eq!(negative, Value::Int(-50));
2139
2140        // Test extreme values
2141        let max_int = Value::Int(i64::MAX);
2142        let min_int = Value::Int(i64::MIN);
2143        assert_eq!(max_int, Value::Int(i64::MAX));
2144        assert_eq!(min_int, Value::Int(i64::MIN));
2145    }
2146
2147    #[test]
2148    fn test_value_bytes() {
2149        let empty_bytes = Value::Bytes(vec![]);
2150        assert_eq!(empty_bytes, Value::Bytes(vec![]));
2151
2152        let some_bytes = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
2153        assert_eq!(some_bytes, Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]));
2154
2155        // Test that different byte sequences are not equal
2156        let other_bytes = Value::Bytes(vec![0x50, 0x4b, 0x03, 0x04]);
2157        assert_ne!(some_bytes, other_bytes);
2158    }
2159
2160    #[test]
2161    fn test_value_string() {
2162        let empty_string = Value::String(String::new());
2163        assert_eq!(empty_string, Value::String(String::new()));
2164
2165        let hello = Value::String("Hello, World!".to_string());
2166        assert_eq!(hello, Value::String("Hello, World!".to_string()));
2167
2168        // Test Unicode strings
2169        let unicode = Value::String("🦀 Rust".to_string());
2170        assert_eq!(unicode, Value::String("🦀 Rust".to_string()));
2171    }
2172
2173    #[test]
2174    fn test_value_comparison() {
2175        // Test that different value types are not equal
2176        let uint_val = Value::Uint(42);
2177        let int_val = Value::Int(42);
2178        let float_val = Value::Float(42.0);
2179        let bytes_val = Value::Bytes(vec![42]);
2180        let string_val = Value::String("42".to_string());
2181
2182        assert_ne!(uint_val, int_val);
2183        assert_ne!(uint_val, float_val);
2184        assert_ne!(uint_val, bytes_val);
2185        assert_ne!(uint_val, string_val);
2186        assert_ne!(int_val, float_val);
2187        assert_ne!(int_val, bytes_val);
2188        assert_ne!(int_val, string_val);
2189        assert_ne!(float_val, bytes_val);
2190        assert_ne!(float_val, string_val);
2191        assert_ne!(bytes_val, string_val);
2192    }
2193
2194    #[test]
2195    fn test_value_debug() {
2196        let uint_val = Value::Uint(123);
2197        let debug_str = format!("{uint_val:?}");
2198        assert!(debug_str.contains("Uint"));
2199        assert!(debug_str.contains("123"));
2200
2201        let string_val = Value::String("test".to_string());
2202        let debug_str = format!("{string_val:?}");
2203        assert!(debug_str.contains("String"));
2204        assert!(debug_str.contains("test"));
2205    }
2206
2207    #[test]
2208    fn test_value_clone() {
2209        let original = Value::Bytes(vec![1, 2, 3, 4]);
2210        let cloned = original.clone();
2211        assert_eq!(original, cloned);
2212
2213        // Verify they are independent copies
2214        match (original, cloned) {
2215            (Value::Bytes(orig_bytes), Value::Bytes(cloned_bytes)) => {
2216                assert_eq!(orig_bytes, cloned_bytes);
2217                // They should have the same content but be different Vec instances
2218            }
2219            _ => panic!("Expected Bytes variants"),
2220        }
2221    }
2222
2223    #[test]
2224    fn test_value_float() {
2225        let value = Value::Float(3.125);
2226        assert_eq!(value, Value::Float(3.125));
2227
2228        let negative = Value::Float(-1.5);
2229        assert_eq!(negative, Value::Float(-1.5));
2230
2231        let zero = Value::Float(0.0);
2232        assert_eq!(zero, Value::Float(0.0));
2233    }
2234
2235    #[test]
2236    fn test_value_serialization() {
2237        let values = vec![
2238            Value::Uint(42),
2239            Value::Int(-100),
2240            Value::Float(3.125),
2241            Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
2242            Value::String("ELF executable".to_string()),
2243        ];
2244
2245        for value in values {
2246            // Test JSON serialization
2247            let json = serde_json::to_string(&value).expect("Failed to serialize Value");
2248            let deserialized: Value =
2249                serde_json::from_str(&json).expect("Failed to deserialize Value");
2250            assert_eq!(value, deserialized);
2251        }
2252    }
2253
2254    #[test]
2255    fn test_value_serialization_edge_cases() {
2256        // Test empty collections
2257        let empty_bytes = Value::Bytes(vec![]);
2258        let json = serde_json::to_string(&empty_bytes).expect("Failed to serialize empty bytes");
2259        let deserialized: Value =
2260            serde_json::from_str(&json).expect("Failed to deserialize empty bytes");
2261        assert_eq!(empty_bytes, deserialized);
2262
2263        let empty_string = Value::String(String::new());
2264        let json = serde_json::to_string(&empty_string).expect("Failed to serialize empty string");
2265        let deserialized: Value =
2266            serde_json::from_str(&json).expect("Failed to deserialize empty string");
2267        assert_eq!(empty_string, deserialized);
2268
2269        // Test extreme values
2270        let max_uint = Value::Uint(u64::MAX);
2271        let json = serde_json::to_string(&max_uint).expect("Failed to serialize max uint");
2272        let deserialized: Value =
2273            serde_json::from_str(&json).expect("Failed to deserialize max uint");
2274        assert_eq!(max_uint, deserialized);
2275
2276        let min_int = Value::Int(i64::MIN);
2277        let json = serde_json::to_string(&min_int).expect("Failed to serialize min int");
2278        let deserialized: Value =
2279            serde_json::from_str(&json).expect("Failed to deserialize min int");
2280        assert_eq!(min_int, deserialized);
2281    }
2282
2283    // TypeKind tests
2284    #[test]
2285    fn test_type_kind_byte() {
2286        let byte_type = TypeKind::Byte { signed: true };
2287        assert_eq!(byte_type, TypeKind::Byte { signed: true });
2288    }
2289
2290    #[test]
2291    fn test_type_kind_short() {
2292        let short_little_endian = TypeKind::Short {
2293            endian: Endianness::Little,
2294            signed: false,
2295        };
2296        let short_big_endian = TypeKind::Short {
2297            endian: Endianness::Big,
2298            signed: true,
2299        };
2300
2301        assert_ne!(short_little_endian, short_big_endian);
2302        assert_eq!(short_little_endian, short_little_endian.clone());
2303    }
2304
2305    #[test]
2306    fn test_type_kind_long() {
2307        let long_native = TypeKind::Long {
2308            endian: Endianness::Native,
2309            signed: true,
2310        };
2311
2312        match long_native {
2313            TypeKind::Long { endian, signed } => {
2314                assert_eq!(endian, Endianness::Native);
2315                assert!(signed);
2316            }
2317            _ => panic!("Expected Long variant"),
2318        }
2319    }
2320
2321    #[test]
2322    fn test_type_kind_string() {
2323        let unlimited_string = TypeKind::String {
2324            max_length: None,
2325            flags: StringFlags::default(),
2326        };
2327        let limited_string = TypeKind::String {
2328            max_length: Some(256),
2329            flags: StringFlags::default(),
2330        };
2331
2332        assert_ne!(unlimited_string, limited_string);
2333        assert_eq!(unlimited_string, unlimited_string.clone());
2334    }
2335
2336    #[test]
2337    fn test_type_kind_serialization() {
2338        let types = vec![
2339            TypeKind::Byte { signed: true },
2340            TypeKind::Short {
2341                endian: Endianness::Little,
2342                signed: false,
2343            },
2344            TypeKind::Long {
2345                endian: Endianness::Big,
2346                signed: true,
2347            },
2348            TypeKind::Quad {
2349                endian: Endianness::Little,
2350                signed: false,
2351            },
2352            TypeKind::Quad {
2353                endian: Endianness::Big,
2354                signed: true,
2355            },
2356            TypeKind::Float {
2357                endian: Endianness::Native,
2358            },
2359            TypeKind::Float {
2360                endian: Endianness::Big,
2361            },
2362            TypeKind::Double {
2363                endian: Endianness::Little,
2364            },
2365            TypeKind::Double {
2366                endian: Endianness::Native,
2367            },
2368            TypeKind::Date {
2369                endian: Endianness::Big,
2370                utc: true,
2371            },
2372            TypeKind::Date {
2373                endian: Endianness::Little,
2374                utc: false,
2375            },
2376            TypeKind::QDate {
2377                endian: Endianness::Native,
2378                utc: true,
2379            },
2380            TypeKind::QDate {
2381                endian: Endianness::Big,
2382                utc: false,
2383            },
2384            TypeKind::String {
2385                max_length: None,
2386                flags: StringFlags::default(),
2387            },
2388            TypeKind::String {
2389                max_length: Some(128),
2390                flags: StringFlags::default(),
2391            },
2392            TypeKind::PString {
2393                max_length: None,
2394                length_width: PStringLengthWidth::OneByte,
2395                length_includes_itself: false,
2396            },
2397            TypeKind::PString {
2398                max_length: Some(64),
2399                length_width: PStringLengthWidth::OneByte,
2400                length_includes_itself: false,
2401            },
2402            TypeKind::PString {
2403                max_length: None,
2404                length_width: PStringLengthWidth::TwoByteBE,
2405                length_includes_itself: true,
2406            },
2407            TypeKind::PString {
2408                max_length: Some(128),
2409                length_width: PStringLengthWidth::FourByteLE,
2410                length_includes_itself: false,
2411            },
2412        ];
2413
2414        for typ in types {
2415            let json = serde_json::to_string(&typ).expect("Failed to serialize TypeKind");
2416            let deserialized: TypeKind =
2417                serde_json::from_str(&json).expect("Failed to deserialize TypeKind");
2418            assert_eq!(typ, deserialized);
2419        }
2420    }
2421
2422    // Operator tests
2423    #[test]
2424    fn test_operator_variants() {
2425        let operators = [
2426            Operator::Equal,
2427            Operator::NotEqual,
2428            Operator::BitwiseAnd,
2429            Operator::BitwiseXor,
2430            Operator::BitwiseNot,
2431            Operator::AnyValue,
2432        ];
2433
2434        for (i, op) in operators.iter().enumerate() {
2435            for (j, other) in operators.iter().enumerate() {
2436                if i == j {
2437                    assert_eq!(op, other);
2438                } else {
2439                    assert_ne!(op, other);
2440                }
2441            }
2442        }
2443    }
2444
2445    #[test]
2446    fn test_operator_serialization() {
2447        let operators = vec![
2448            Operator::Equal,
2449            Operator::NotEqual,
2450            Operator::BitwiseAnd,
2451            Operator::BitwiseXor,
2452            Operator::BitwiseNot,
2453            Operator::AnyValue,
2454        ];
2455
2456        for op in operators {
2457            let json = serde_json::to_string(&op).expect("Failed to serialize Operator");
2458            let deserialized: Operator =
2459                serde_json::from_str(&json).expect("Failed to deserialize Operator");
2460            assert_eq!(op, deserialized);
2461        }
2462    }
2463
2464    // MagicRule tests
2465    #[test]
2466    fn test_magic_rule_creation() {
2467        let rule = MagicRule {
2468            offset: OffsetSpec::Absolute(0),
2469            typ: TypeKind::Byte { signed: true },
2470            op: Operator::Equal,
2471            value: Value::Uint(0x7f),
2472            message: "ELF magic".to_string(),
2473            children: vec![],
2474            level: 0,
2475            strength_modifier: None,
2476            value_transform: None,
2477        };
2478
2479        assert_eq!(rule.message, "ELF magic");
2480        assert_eq!(rule.level, 0);
2481        assert!(rule.children.is_empty());
2482    }
2483
2484    #[test]
2485    fn test_magic_rule_with_children() {
2486        let child_rule = MagicRule {
2487            offset: OffsetSpec::Absolute(4),
2488            typ: TypeKind::Byte { signed: true },
2489            op: Operator::Equal,
2490            value: Value::Uint(1),
2491            message: "32-bit".to_string(),
2492            children: vec![],
2493            level: 1,
2494            strength_modifier: None,
2495            value_transform: None,
2496        };
2497
2498        let parent_rule = MagicRule {
2499            offset: OffsetSpec::Absolute(0),
2500            typ: TypeKind::Long {
2501                endian: Endianness::Little,
2502                signed: false,
2503            },
2504            op: Operator::Equal,
2505            value: Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
2506            message: "ELF executable".to_string(),
2507            children: vec![child_rule],
2508            level: 0,
2509            strength_modifier: None,
2510            value_transform: None,
2511        };
2512
2513        assert_eq!(parent_rule.children.len(), 1);
2514        assert_eq!(parent_rule.children[0].level, 1);
2515        assert_eq!(parent_rule.children[0].message, "32-bit");
2516    }
2517
2518    #[test]
2519    fn test_magic_rule_serialization() {
2520        let rule = MagicRule {
2521            offset: OffsetSpec::Absolute(16),
2522            typ: TypeKind::Short {
2523                endian: Endianness::Little,
2524                signed: false,
2525            },
2526            op: Operator::NotEqual,
2527            value: Value::Uint(0),
2528            message: "Non-zero short value".to_string(),
2529            children: vec![],
2530            level: 2,
2531            strength_modifier: None,
2532            value_transform: None,
2533        };
2534
2535        let json = serde_json::to_string(&rule).expect("Failed to serialize MagicRule");
2536        let deserialized: MagicRule =
2537            serde_json::from_str(&json).expect("Failed to deserialize MagicRule");
2538
2539        assert_eq!(rule.message, deserialized.message);
2540        assert_eq!(rule.level, deserialized.level);
2541        assert_eq!(rule.children.len(), deserialized.children.len());
2542    }
2543
2544    // StrengthModifier tests
2545    #[test]
2546    fn test_strength_modifier_variants() {
2547        let add = StrengthModifier::Add(10);
2548        let sub = StrengthModifier::Subtract(5);
2549        let mul = StrengthModifier::Multiply(2);
2550        let div = StrengthModifier::Divide(2);
2551        let set = StrengthModifier::Set(50);
2552
2553        // Test that each variant has the correct inner value
2554        assert_eq!(add, StrengthModifier::Add(10));
2555        assert_eq!(sub, StrengthModifier::Subtract(5));
2556        assert_eq!(mul, StrengthModifier::Multiply(2));
2557        assert_eq!(div, StrengthModifier::Divide(2));
2558        assert_eq!(set, StrengthModifier::Set(50));
2559
2560        // Test that different variants are not equal
2561        assert_ne!(add, sub);
2562        assert_ne!(mul, div);
2563        assert_ne!(set, add);
2564    }
2565
2566    #[test]
2567    fn test_strength_modifier_negative_values() {
2568        let add_negative = StrengthModifier::Add(-10);
2569        let sub_negative = StrengthModifier::Subtract(-5);
2570        let set_negative = StrengthModifier::Set(-50);
2571
2572        assert_eq!(add_negative, StrengthModifier::Add(-10));
2573        assert_eq!(sub_negative, StrengthModifier::Subtract(-5));
2574        assert_eq!(set_negative, StrengthModifier::Set(-50));
2575    }
2576
2577    #[test]
2578    fn test_strength_modifier_serialization() {
2579        let modifiers = vec![
2580            StrengthModifier::Add(10),
2581            StrengthModifier::Subtract(5),
2582            StrengthModifier::Multiply(2),
2583            StrengthModifier::Divide(3),
2584            StrengthModifier::Set(100),
2585        ];
2586
2587        for modifier in modifiers {
2588            let json =
2589                serde_json::to_string(&modifier).expect("Failed to serialize StrengthModifier");
2590            let deserialized: StrengthModifier =
2591                serde_json::from_str(&json).expect("Failed to deserialize StrengthModifier");
2592            assert_eq!(modifier, deserialized);
2593        }
2594    }
2595
2596    #[test]
2597    fn test_strength_modifier_debug() {
2598        let modifier = StrengthModifier::Add(25);
2599        let debug_str = format!("{modifier:?}");
2600        assert!(debug_str.contains("Add"));
2601        assert!(debug_str.contains("25"));
2602    }
2603
2604    #[test]
2605    fn test_strength_modifier_clone() {
2606        let original = StrengthModifier::Multiply(4);
2607        let cloned = original;
2608        assert_eq!(original, cloned);
2609    }
2610
2611    #[test]
2612    fn test_magic_rule_with_strength_modifier() {
2613        let rule = MagicRule {
2614            offset: OffsetSpec::Absolute(0),
2615            typ: TypeKind::Byte { signed: true },
2616            op: Operator::Equal,
2617            value: Value::Uint(0x7f),
2618            message: "ELF magic".to_string(),
2619            children: vec![],
2620            level: 0,
2621            strength_modifier: Some(StrengthModifier::Add(20)),
2622            value_transform: None,
2623        };
2624
2625        assert_eq!(rule.strength_modifier, Some(StrengthModifier::Add(20)));
2626
2627        // Test serialization with strength_modifier
2628        let json = serde_json::to_string(&rule).expect("Failed to serialize MagicRule");
2629        let deserialized: MagicRule =
2630            serde_json::from_str(&json).expect("Failed to deserialize MagicRule");
2631        assert_eq!(rule.strength_modifier, deserialized.strength_modifier);
2632    }
2633
2634    #[test]
2635    fn test_magic_rule_without_strength_modifier() {
2636        let rule = MagicRule {
2637            offset: OffsetSpec::Absolute(0),
2638            typ: TypeKind::Byte { signed: true },
2639            op: Operator::Equal,
2640            value: Value::Uint(0x7f),
2641            message: "ELF magic".to_string(),
2642            children: vec![],
2643            level: 0,
2644            strength_modifier: None,
2645            value_transform: None,
2646        };
2647
2648        assert_eq!(rule.strength_modifier, None);
2649    }
2650
2651    // MetaType tests
2652    #[test]
2653    fn test_meta_type_variants_debug_clone_eq() {
2654        let cases = [
2655            MetaType::Default,
2656            MetaType::Clear,
2657            MetaType::Indirect,
2658            MetaType::Offset,
2659            MetaType::Name("part2".to_string()),
2660            MetaType::Use {
2661                name: "part2".to_string(),
2662                flip_endian: false,
2663            },
2664            // A `\^`-flipped use must be distinct from the plain form so the
2665            // endian-flip flag participates in equality/serde/round-trip.
2666            MetaType::Use {
2667                name: "part2".to_string(),
2668                flip_endian: true,
2669            },
2670        ];
2671
2672        for (i, variant) in cases.iter().enumerate() {
2673            // Debug formatting is non-empty
2674            let debug_str = format!("{variant:?}");
2675            assert!(
2676                !debug_str.is_empty(),
2677                "Debug format must be non-empty for variant at index {i}"
2678            );
2679
2680            // Clone round-trip preserves equality
2681            let cloned = variant.clone();
2682            assert_eq!(
2683                variant, &cloned,
2684                "Clone must preserve equality for variant at index {i}"
2685            );
2686
2687            // Distinct variants are not equal
2688            for (j, other) in cases.iter().enumerate() {
2689                if i == j {
2690                    assert_eq!(variant, other);
2691                } else {
2692                    assert_ne!(
2693                        variant, other,
2694                        "Variants at indices {i} and {j} must differ"
2695                    );
2696                }
2697            }
2698        }
2699    }
2700
2701    #[test]
2702    fn test_meta_type_serde_roundtrip() {
2703        let cases = [
2704            MetaType::Default,
2705            MetaType::Clear,
2706            MetaType::Indirect,
2707            MetaType::Offset,
2708            MetaType::Name("foo".to_string()),
2709            MetaType::Use {
2710                name: "bar".to_string(),
2711                flip_endian: false,
2712            },
2713            MetaType::Use {
2714                name: "bar".to_string(),
2715                flip_endian: true,
2716            },
2717        ];
2718
2719        for variant in cases {
2720            let json = serde_json::to_string(&variant).expect("serialize MetaType");
2721            let deserialized: MetaType = serde_json::from_str(&json).expect("deserialize MetaType");
2722            assert_eq!(variant, deserialized);
2723        }
2724    }
2725
2726    #[test]
2727    fn test_type_kind_meta_bit_width_is_none() {
2728        let cases = [
2729            MetaType::Default,
2730            MetaType::Clear,
2731            MetaType::Indirect,
2732            MetaType::Offset,
2733            MetaType::Name("x".to_string()),
2734            MetaType::Use {
2735                name: "x".to_string(),
2736                flip_endian: false,
2737            },
2738            MetaType::Use {
2739                name: "x".to_string(),
2740                flip_endian: true,
2741            },
2742        ];
2743        for meta in cases {
2744            let kind = TypeKind::Meta(meta);
2745            assert_eq!(
2746                kind.bit_width(),
2747                None,
2748                "TypeKind::Meta must have no bit width: {kind:?}"
2749            );
2750        }
2751    }
2752}