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