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