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