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}