forensicnomicon-core 1.4.0

Stable engine layer of the ForensicNomicon: the normalized DFIR report model (Finding/Severity/Observation) and structural format constants. Zero deps.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
//! Filesystem superblock / boot-sector magic signatures.
//!
//! Single source of truth mapping a `(offset, magic-bytes)` pair to a filesystem
//! name, for forensic tools that fingerprint a partition's content. Each entry
//! cites the authoritative on-disk-format reference for its offset and magic.
//!
//! Offsets are absolute byte offsets from the start of the partition/volume.
//!
//! General references:
//! - Wikipedia, "List of file signatures": <https://en.wikipedia.org/wiki/List_of_file_signatures>
//! - The `file`/libmagic database (`magic/Magdir/filesystems`):
//!   <https://github.com/file/file/blob/master/magic/Magdir/filesystems>

/// One filesystem magic signature.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FsSignature {
    /// Filesystem name.
    pub name: &'static str,
    /// Absolute byte offset of the magic within the volume.
    pub offset: usize,
    /// The magic bytes expected at `offset`.
    pub magic: &'static [u8],
}

/// Well-known filesystem magic signatures.
///
/// Per-entry sources:
/// - **ext2/3/4** — superblock magic `0xEF53` (LE `53 EF`) at offset `0x438`:
///   Linux kernel `fs/ext4/ext4.h` `EXT4_SUPER_MAGIC`; Wikipedia "Ext4".
/// - **NTFS** — OEM ID `"NTFS    "` at offset `3`: Microsoft NTFS docs; Wikipedia "NTFS".
/// - **exFAT** — OEM name `"EXFAT   "` at offset `3`: Microsoft exFAT specification.
/// - **XFS** — superblock magic `"XFSB"` at offset `0`: XFS Algorithms & Data
///   Structures (SGI/Red Hat).
/// - **LUKS1** — magic `"LUKS\xba\xbe"` at offset `0`: LUKS On-Disk Format Spec.
/// - **FAT32** — `BS_FilSysType` `"FAT32   "` at offset `0x52`: Microsoft FAT
///   Specification (`fatgen103`).
/// - **FAT16/FAT12** — `BS_FilSysType` `"FAT16   "` / `"FAT12   "` at offset `0x36`:
///   Microsoft FAT Specification.
/// - **Linux swap** — `"SWAPSPACE2"` at offset `0xFF6` (page end − 10): `mkswap(8)` / kernel.
/// - **ISO 9660** — `"CD001"` at offset `0x8001` (sector 16): ECMA-119.
/// - **HFS+** — signature `"H+"` at offset `0x400`: Apple Technical Note TN1150.
/// - **APFS** — container superblock `nx_magic` `"NXSB"` at offset `32` (after the
///   32-byte `obj_phys_t` header): Apple File System Reference; util-linux
///   `libblkid/src/superblocks/apfs.c` (`.magic = "NXSB", .sboff = 32`).
/// - **Btrfs** — superblock magic `"_BHRfS_M"` at offset `65600` (`0x10040`; the
///   superblock is at 64 KiB and `magic` is at `+0x40`): util-linux
///   `libblkid/src/superblocks/btrfs.c` (`.kboff = 64, .sboff = 0x40`).
/// - **LVM2 PV** — label `"LABELONE"` at the start of the PV label sector, which
///   is sector 0 **or** sector 1 (offset `0` or `512`; the default is sector 1):
///   util-linux `libblkid/src/superblocks/lvm.c` (checks `buf` and `buf + 512`);
///   libvslvm "Logical Volume Manager (LVM) format".
/// - **UFS1** — `fs_magic` `0x00011954` (LE `54 19 01 00`) at offset `9564`:
///   util-linux `libblkid/src/superblocks/ufs.c` reads `fs_magic` at
///   `offsets[i]*1024 + offsetof(struct ufs_super_block, fs_magic)`, with
///   `offsets[] = {0, 8, 64, 256}` (KiB) and `offsetof(fs_magic) = 1372` (the
///   final field of the 1376-byte superblock). The canonical UFS1 primary
///   superblock is `SBLOCK_UFS1 = 8192`, so `8192 + 1372 = 9564`. Magic
///   `UFS_MAGIC = 0x00011954`; FreeBSD `sys/ufs/ffs/fs.h` (`FS_UFS1_MAGIC`,
///   `SBLOCK_UFS1`). Source:
///   <https://raw.githubusercontent.com/util-linux/util-linux/master/libblkid/src/superblocks/ufs.c>
/// - **UFS2** — `fs_magic` `0x19540119` (LE `19 01 54 19`) at offset `66908`:
///   same `ufs.c` reader; UFS2 primary superblock is `SBLOCK_UFS2 = 65536`, so
///   `65536 + 1372 = 66908`. Magic `UFS2_MAGIC = 0x19540119`; FreeBSD
///   `sys/ufs/ffs/fs.h` (`FS_UFS2_MAGIC`, `SBLOCK_UFS2`).
/// - **ReFS** — magic `"\0\0\0ReFS\0"` (8 bytes) at offset `0`: util-linux
///   `libblkid/src/superblocks/refs.c`
///   (`{ .magic = "\000\000\000ReFS\000", .len = 8 }`, `kboff`/`sboff` default
///   `0`). The `"ReFS"` string sits at byte `3` (the OEM-ID field NTFS/FAT use,
///   zeroed on ReFS) framed by leading `00 00 00` and a trailing `00`; the
///   `"FSRS"` File-System-Recognition-Structure id follows at `0x10`. DFRWS 2020
///   "Forensic Analysis of the Resilient File System (ReFS) Version 3.4" (Prade
///   et al.); <https://www.resilientfilesystem.co.uk/refs-volume-boot-record>.
///   Source:
///   <https://raw.githubusercontent.com/util-linux/util-linux/master/libblkid/src/superblocks/refs.c>
/// - **UDF** — Volume-Structure-Descriptor `stdIdent` `"NSR02"`/`"NSR03"` at
///   offset `0x8801` (`34817`): the ECMA-167 Volume Recognition Sequence begins
///   at sector 16 (`UDF_VSD_OFFSET = 0x8000`); each 2048-byte descriptor is
///   `structType`(1 byte) + `stdIdent[5]`, so `stdIdent` is at `+1`. The
///   UDF-defining NSR descriptor follows `BEA01` at sector 17, giving
///   `0x8000 + 2048 + 1 = 0x8801`. util-linux `libblkid/src/superblocks/udf.c`
///   scans up to 64 VRS descriptors (`.magic = "NSR02"/"NSR03", .kboff = 32,
///   .sboff = 1`) and treats NSR02/NSR03 as the positive UDF match; ECMA-167
///   3rd ed. 2/9.1, 3/9.1. This fixed-offset entry captures the standard
///   layout (BEA01→NSR→TEA01, verified on a real macOS `newfs_udf` image with
///   `NSR03` at `0x8801`); a UDF-bridged disc that pushes NSR past sector 17
///   needs the multi-sector VRS scan libblkid performs. Source:
///   <https://raw.githubusercontent.com/util-linux/util-linux/master/libblkid/src/superblocks/udf.c>
/// - **ZFS** — *not in this table by design.* ZFS has no single fixed-offset
///   magic the [`FsSignature`] struct can carry: util-linux
///   `libblkid/src/superblocks/zfs.c` uses `.magics = BLKID_NONE_MAGIC` and
///   `probe_zfs` scans **four** 256-KiB vdev labels (two at the device start,
///   two at the device *end*, so their offsets depend on the device size). The
///   uberblock magic `0x00bab10c` (`ub_magic`, host-endian — both LE
///   `0c b1 ba 00` and BE `00 ba b1 0c` occur) lives in each label's **uberblock
///   ring** at label-offset `+128 KiB`, but the *active* slot is
///   `txg % slot_count`, so the magic's byte position is **data-dependent**
///   (on the OpenZFS `zol-0.6.1` real label the magic first appears at `0x21000`,
///   not at the ring start `0x20000`, which is zeros — a fixed-offset entry would
///   false-negative). ZFS is therefore detected by the structural [`detect_zfs`]
///   scan, which [`detect_name`] falls through to after this table misses.
///   Source:
///   <https://raw.githubusercontent.com/util-linux/util-linux/master/libblkid/src/superblocks/zfs.c>
pub const FILESYSTEM_SIGNATURES: &[FsSignature] = &[
    FsSignature {
        name: "ext2/3/4",
        offset: 0x438,
        magic: &[0x53, 0xEF],
    },
    FsSignature {
        name: "NTFS",
        offset: 3,
        magic: b"NTFS    ",
    },
    FsSignature {
        name: "exFAT",
        offset: 3,
        magic: b"EXFAT   ",
    },
    FsSignature {
        name: "XFS",
        offset: 0,
        magic: b"XFSB",
    },
    FsSignature {
        name: "LUKS",
        offset: 0,
        magic: b"LUKS\xba\xbe",
    },
    FsSignature {
        name: "APFS",
        offset: 32,
        magic: b"NXSB",
    },
    FsSignature {
        name: "FAT32",
        offset: 0x52,
        magic: b"FAT32   ",
    },
    FsSignature {
        name: "FAT16",
        offset: 0x36,
        magic: b"FAT16   ",
    },
    FsSignature {
        name: "FAT12",
        offset: 0x36,
        magic: b"FAT12   ",
    },
    FsSignature {
        name: "Linux swap",
        offset: 0xFF6,
        magic: b"SWAPSPACE2",
    },
    FsSignature {
        name: "LVM2",
        offset: 0,
        magic: b"LABELONE",
    },
    FsSignature {
        name: "LVM2",
        offset: 512,
        magic: b"LABELONE",
    },
    FsSignature {
        name: "ISO 9660",
        offset: 0x8001,
        magic: b"CD001",
    },
    FsSignature {
        name: "HFS+",
        offset: 0x400,
        magic: b"H+",
    },
    FsSignature {
        name: "Btrfs",
        offset: 65600,
        magic: b"_BHRfS_M",
    },
    FsSignature {
        name: "UFS1",
        offset: 9564,
        magic: &[0x54, 0x19, 0x01, 0x00],
    },
    FsSignature {
        name: "UFS2",
        offset: 66908,
        magic: &[0x19, 0x01, 0x54, 0x19],
    },
    FsSignature {
        name: "ReFS",
        offset: 0,
        magic: b"\x00\x00\x00ReFS\x00",
    },
    FsSignature {
        name: "UDF",
        offset: 0x8801,
        magic: b"NSR02",
    },
    FsSignature {
        name: "UDF",
        offset: 0x8801,
        magic: b"NSR03",
    },
];

/// Identify the filesystem from a volume's leading bytes, returning the first
/// matching signature's name. Returns `None` when nothing matches (the slice may
/// simply be too short to reach a deeper magic).
///
/// ZFS has no fixed-offset magic the [`FsSignature`] table can carry (see the
/// [`FILESYSTEM_SIGNATURES`] doc comment), so after the fixed-offset table
/// misses this falls through to the structural [`detect_zfs`] scan and reports
/// `"ZFS"` on a hit.
#[must_use]
pub fn detect_name(data: &[u8]) -> Option<&'static str> {
    FILESYSTEM_SIGNATURES
        .iter()
        .find_map(|sig| {
            let end = sig.offset.checked_add(sig.magic.len())?;
            (data.len() >= end && &data[sig.offset..end] == sig.magic).then_some(sig.name)
        })
        .or_else(|| detect_zfs(data).then_some("ZFS"))
}

/// Device offset of the L0 vdev label (labels L0/L1 sit at the device start).
const ZFS_L0_LABEL_OFFSET: usize = 0;
/// Offset of the uberblock ring within a vdev label (`VDEV_LABEL_NVPAIR` end).
const ZFS_UBERBLOCK_RING_OFFSET: usize = 128 * 1024;
/// A vdev label is 256 KiB (`VDEV_LABEL_SIZE`).
const ZFS_VDEV_LABEL_SIZE: usize = 256 * 1024;
/// Smallest uberblock slot (1 KiB at the default ashift); larger slots (up to
/// 8 KiB) still start on a 1 KiB boundary, so scanning at this stride reaches
/// every possible active slot.
const ZFS_UBERBLOCK_MIN_SLOT: usize = 1024;
/// ZFS uberblock magic `0x00bab10c` (`ub_magic`), little-endian byte order.
const ZFS_UBERBLOCK_MAGIC_LE: [u8; 4] = [0x0c, 0xb1, 0xba, 0x00];
/// ZFS uberblock magic `0x00bab10c` (`ub_magic`), big-endian byte order.
const ZFS_UBERBLOCK_MAGIC_BE: [u8; 4] = [0x00, 0xba, 0xb1, 0x0c];

/// Structural ZFS detector: scan the **L0 vdev label's uberblock ring** for the
/// `0x00bab10c` uberblock magic (`ub_magic`) in **either** endianness.
///
/// ZFS writes no single fixed-offset magic (unlike the [`FsSignature`] table
/// entries): a vdev label is 256 KiB and its uberblock ring begins at label
/// offset `+128 KiB`. Uberblocks are slot-sized (1 KiB by default, up to 8 KiB
/// by `ashift`) and the *active* slot is `txg % slot_count`, so the magic's byte
/// position is **data-dependent** — a single fixed offset false-negatives (on a
/// real OpenZFS `zol-0.6.1` label the magic first appears at `0x21000`, not at
/// the ring start `0x20000`, which is zeros). So this scans the whole ring
/// region — device offsets `0x20000..0x40000` (label end) — at a 1 KiB stride
/// (the smallest slot boundary, which every larger slot also lands on) and
/// returns `true` on the first magic in either byte order.
///
/// The magic is stored **host-endian**, so both little-endian (`0c b1 ba 00`)
/// and big-endian (`00 ba b1 0c`) occur in the wild and both are matched. Every
/// access is bounds-checked (via `slice::get`), so a short or truncated slice
/// yields `false` rather than panicking, and no allocation is performed.
///
/// # Scope and known limitation
///
/// This is a **partial, heuristic** check: it inspects only the **L0 label at
/// device offset 0** and matches the uberblock magic — it does **not** validate
/// the XDR NVList header the way libblkid's `probe_zfs` does (`nvh_encoding ==
/// 0x1`), so it is a lighter (but accepted) signal than a full NVList parse. A
/// device whose front labels are wiped (leaving only the two labels ZFS mirrors
/// at the *device end*, whose offsets depend on the total device size) is **not**
/// covered here and would need an end-of-device label scan.
///
/// Cross-checked against util-linux `libblkid/src/superblocks/zfs.c`
/// (`VDEV_LABEL_SIZE = 256 KiB`, `VDEV_LABEL_NVPAIR = 16 KiB`, four labels, the
/// "128x1kB host-endian root blocks... #4 @ 132kB is the first one written"
/// comment) and the OpenZFS on-disk-format `uberblock_t` / `ub_magic`
/// definition. Source:
/// <https://raw.githubusercontent.com/util-linux/util-linux/master/libblkid/src/superblocks/zfs.c>
#[must_use]
pub fn detect_zfs(data: &[u8]) -> bool {
    let ring_start = ZFS_L0_LABEL_OFFSET + ZFS_UBERBLOCK_RING_OFFSET;
    let ring_end = ZFS_L0_LABEL_OFFSET + ZFS_VDEV_LABEL_SIZE;
    let mut off = ring_start;
    while off < ring_end {
        if let Some(slot) = data.get(off..off + 4) {
            if slot == ZFS_UBERBLOCK_MAGIC_LE || slot == ZFS_UBERBLOCK_MAGIC_BE {
                return true;
            }
        }
        off += ZFS_UBERBLOCK_MIN_SLOT;
    }
    false
}

/// Canonical identity of a filesystem, content-addressed by a stable lowercase
/// name.
///
/// A newtype over `&'static str` (not an enum) so the set is **open**: adding a
/// filesystem is a new `const` here, never a breaking change to a downstream
/// contract crate. Every filesystem is a uniform `const` — none is a
/// first-class variant and none is a stringly-typed `Other`.
///
/// Intended as the single source of the identity that `forensic-vfs::FsKind`
/// re-exports, retiring its named-variants-plus-`Other` enum.
///
/// The names here are the canonical *identity* labels; they intentionally
/// differ from the human-facing detection names in [`FILESYSTEM_SIGNATURES`]
/// (e.g. identity `ext` vs. the signature label `ext2/3/4`). Identity and
/// detection are kept as two separate concerns; this type carries no magics.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct FsKind(&'static str);

impl FsKind {
    /// NTFS.
    pub const NTFS: FsKind = FsKind("ntfs");
    /// FAT (FAT12/16/32 family).
    pub const FAT: FsKind = FsKind("fat");
    /// exFAT.
    pub const EXFAT: FsKind = FsKind("exfat");
    /// ext2/3/4 family.
    pub const EXT: FsKind = FsKind("ext");
    /// XFS.
    pub const XFS: FsKind = FsKind("xfs");
    /// Apple APFS.
    pub const APFS: FsKind = FsKind("apfs");
    /// Apple HFS+.
    pub const HFS_PLUS: FsKind = FsKind("hfsplus");
    /// ISO 9660 optical filesystem.
    pub const ISO9660: FsKind = FsKind("iso9660");
    /// UDF optical filesystem.
    pub const UDF: FsKind = FsKind("udf");
    /// Btrfs.
    pub const BTRFS: FsKind = FsKind("btrfs");
    /// ZFS.
    pub const ZFS: FsKind = FsKind("zfs");
    /// UFS/FFS.
    pub const UFS: FsKind = FsKind("ufs");
    /// Microsoft ReFS.
    pub const REFS: FsKind = FsKind("refs");
    /// ZIP archive-as-container.
    pub const ZIP: FsKind = FsKind("zip");
    /// AccessData AD1 logical-image container.
    pub const AD1: FsKind = FsKind("ad1");
    /// DAR (Disk ARchive) container.
    pub const DAR: FsKind = FsKind("dar");

    /// Documented fallback for a name outside [`known`](FsKind::known). A
    /// runtime string cannot be promoted to a `&'static str` without leaking, so
    /// deserializing an unrecognized name collapses to this single sentinel
    /// (the first registered kind) rather than allocating or panicking. Callers
    /// needing strict validation compare the input against
    /// [`known`](FsKind::known) before trusting a deserialized value.
    pub const UNKNOWN_FALLBACK: FsKind = FsKind::NTFS;

    /// The stable lowercase identifier — round-trips, safe for logs / JSON / URIs.
    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        self.0
    }

    /// Construct from a compile-time name. Returns a kind wrapping the given
    /// static string; pass one of the const names to get a registered kind.
    #[must_use]
    pub const fn from_name(name: &'static str) -> FsKind {
        FsKind(name)
    }

    /// All registered kinds — lets consumers enumerate/validate without a closed
    /// enum.
    ///
    /// Not `const fn`: referencing a `static` from a `const fn` (`const_refs_to_static`)
    /// only stabilized in Rust 1.83, and this crate's MSRV is 1.75. A plain `fn` returning
    /// the `'static` slice works on every supported toolchain.
    #[must_use]
    pub fn known() -> &'static [FsKind] {
        KNOWN
    }
}

static KNOWN: &[FsKind] = &[
    FsKind::NTFS,
    FsKind::FAT,
    FsKind::EXFAT,
    FsKind::EXT,
    FsKind::XFS,
    FsKind::APFS,
    FsKind::HFS_PLUS,
    FsKind::ISO9660,
    FsKind::UDF,
    FsKind::BTRFS,
    FsKind::ZFS,
    FsKind::UFS,
    FsKind::REFS,
    FsKind::ZIP,
    FsKind::AD1,
    FsKind::DAR,
];

impl core::fmt::Display for FsKind {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(self.0)
    }
}

impl core::fmt::Debug for FsKind {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_tuple("FsKind").field(&self.0).finish()
    }
}

#[cfg(feature = "serde")]
impl serde::Serialize for FsKind {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(self.0)
    }
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for FsKind {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        // Borrow the bare string; resolve it against the registered kinds so the
        // result holds a `&'static str`, never runtime-owned memory. An
        // unrecognized name maps to `UNKNOWN_FALLBACK` (see its docs).
        let name: &str = <&str as serde::Deserialize>::deserialize(deserializer)?;
        Ok(KNOWN
            .iter()
            .copied()
            .find(|k| k.0 == name)
            .unwrap_or(FsKind::UNKNOWN_FALLBACK))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn buf_with(offset: usize, magic: &[u8]) -> Vec<u8> {
        let mut v = vec![0u8; offset + magic.len() + 4];
        v[offset..offset + magic.len()].copy_from_slice(magic);
        v
    }

    #[test]
    fn detects_ext_at_0x438() {
        assert_eq!(
            detect_name(&buf_with(0x438, &[0x53, 0xEF])),
            Some("ext2/3/4")
        );
    }

    #[test]
    fn detects_ntfs_and_exfat_oem() {
        assert_eq!(detect_name(&buf_with(3, b"NTFS    ")), Some("NTFS"));
        assert_eq!(detect_name(&buf_with(3, b"EXFAT   ")), Some("exFAT"));
    }

    #[test]
    fn detects_luks_and_xfs_at_zero() {
        assert_eq!(detect_name(&buf_with(0, b"LUKS\xba\xbe")), Some("LUKS"));
        assert_eq!(detect_name(&buf_with(0, b"XFSB")), Some("XFS"));
    }

    #[test]
    fn detects_apfs_at_offset_32() {
        // libblkid: NXSB at sboff 32 (after the 32-byte obj_phys header).
        assert_eq!(detect_name(&buf_with(32, b"NXSB")), Some("APFS"));
        // NXSB at offset 0 is NOT APFS (the common off-by-32 mistake).
        assert_eq!(detect_name(&buf_with(0, b"NXSB")), None);
    }

    #[test]
    fn detects_btrfs_at_65600() {
        // libblkid: _BHRfS_M at kboff 64 + sboff 0x40 = 65600.
        assert_eq!(detect_name(&buf_with(65600, b"_BHRfS_M")), Some("Btrfs"));
        assert_eq!(detect_name(&buf_with(65536, b"_BHRfS_M")), None);
    }

    #[test]
    fn detects_lvm_at_sector_0_or_1() {
        assert_eq!(detect_name(&buf_with(0, b"LABELONE")), Some("LVM2"));
        // The default PV label is in sector 1 (offset 512) — the case mbr missed.
        assert_eq!(detect_name(&buf_with(512, b"LABELONE")), Some("LVM2"));
    }

    #[test]
    fn detects_ufs1_at_9564() {
        // libblkid `ufs.c`: `fs_magic` (offset 1372 in `struct fs`) checked at
        // superblock KiB positions {0,8,64,256}. The canonical UFS1 primary
        // superblock is at 8192 (`SBLOCK_UFS1`), so `fs_magic` = 8192 + 1372 =
        // 9564. Magic `UFS_MAGIC = 0x00011954`, little-endian on disk.
        assert_eq!(
            detect_name(&buf_with(9564, &[0x54, 0x19, 0x01, 0x00])),
            Some("UFS1")
        );
        // Same magic at the wrong offset (e.g. the superblock start, not
        // `fs_magic`) must not match.
        assert_eq!(
            detect_name(&buf_with(8192, &[0x54, 0x19, 0x01, 0x00])),
            None
        );
    }

    #[test]
    fn detects_ufs2_at_66908() {
        // libblkid `ufs.c`: UFS2 primary superblock at 65536 (`SBLOCK_UFS2`), so
        // `fs_magic` = 65536 + 1372 = 66908. Magic `UFS2_MAGIC = 0x19540119`, LE.
        assert_eq!(
            detect_name(&buf_with(66908, &[0x19, 0x01, 0x54, 0x19])),
            Some("UFS2")
        );
        assert_eq!(
            detect_name(&buf_with(65536, &[0x19, 0x01, 0x54, 0x19])),
            None
        );
    }

    #[test]
    fn detects_refs_at_zero() {
        // libblkid `refs.c`: magic `"\0\0\0ReFS\0"` (len 8) at offset 0 — the
        // OEM-ID field (offset 3, zeroed by NTFS/FAT) holds NUL-framed "ReFS".
        assert_eq!(
            detect_name(&buf_with(0, b"\x00\x00\x00ReFS\x00")),
            Some("ReFS")
        );
        // "ReFS" at offset 3 WITHOUT the trailing NUL frame (e.g. a stray string)
        // must not match the full 8-byte pattern.
        assert_eq!(detect_name(&buf_with(3, b"ReFSxxxx")), None);
    }

    #[test]
    fn detects_udf_nsr02_nsr03_at_0x8801() {
        // libblkid `udf.c` / ECMA-167: the Volume Recognition Sequence begins at
        // sector 16 (0x8000); each 2048-byte descriptor is `structType`(1) +
        // `stdIdent[5]`. The UDF-defining NSR descriptor sits at sector 17, so its
        // 5-byte id is at 0x8000 + 2048 + 1 = 0x8801 (34817). Verified on a real
        // macOS `newfs_udf` image (NSR03 at 0x8801).
        assert_eq!(detect_name(&buf_with(0x8801, b"NSR02")), Some("UDF"));
        assert_eq!(detect_name(&buf_with(0x8801, b"NSR03")), Some("UDF"));
        // A bare "NSR" prefix or wrong id at the same offset must not match.
        assert_eq!(detect_name(&buf_with(0x8801, b"NSRxx")), None);
    }

    #[test]
    fn empty_and_unknown_are_none() {
        assert_eq!(detect_name(&[]), None);
        assert_eq!(detect_name(&[0u8; 512]), None);
    }

    /// Build a 256 KiB L0 vdev label with the uberblock magic placed at a given
    /// device offset, in the requested endianness.
    fn zfs_label_with_magic(offset: usize, be: bool) -> Vec<u8> {
        let mut v = vec![0u8; 256 * 1024];
        let magic: [u8; 4] = if be {
            [0x00, 0xba, 0xb1, 0x0c]
        } else {
            [0x0c, 0xb1, 0xba, 0x00]
        };
        v[offset..offset + 4].copy_from_slice(&magic);
        v
    }

    #[test]
    fn detect_zfs_finds_le_magic_in_uberblock_ring() {
        // Tier-3 (synthetic): magic at 0x21000 (the real-label first-write slot),
        // little-endian `0c b1 ba 00`. Ring start 0x20000 is zeros, proving the
        // scan (not a single fixed offset) is what finds it.
        let label = zfs_label_with_magic(0x21000, false);
        assert!(detect_zfs(&label));
        assert_eq!(detect_name(&label), Some("ZFS"));
    }

    #[test]
    fn detect_zfs_finds_be_magic_in_uberblock_ring() {
        // Tier-3 (synthetic): host-endian ZFS also writes big-endian
        // `00 ba b1 0c`; both must match. Placed at ring start 0x20000.
        let label = zfs_label_with_magic(0x20000, true);
        assert!(detect_zfs(&label));
        assert_eq!(detect_name(&label), Some("ZFS"));
    }

    #[test]
    fn detect_zfs_finds_magic_at_ring_end() {
        // Tier-3: last scanned 1 KiB slot before the label end (0x40000 - 0x400).
        let label = zfs_label_with_magic(0x40000 - 0x400, false);
        assert!(detect_zfs(&label));
    }

    #[test]
    fn detect_zfs_rejects_zeros_and_wrong_magic() {
        // Tier-3 negatives: all-zeros, and the magic sitting *before* the ring
        // (at offset 0) must NOT be detected as ZFS.
        assert!(!detect_zfs(&vec![0u8; 256 * 1024]));
        let mut before_ring = vec![0u8; 256 * 1024];
        before_ring[0..4].copy_from_slice(&[0x0c, 0xb1, 0xba, 0x00]);
        assert!(!detect_zfs(&before_ring));
        // Magic present but at a non-1-KiB-aligned position within the ring is
        // not on any slot boundary, so the stride scan does not spuriously hit.
        let mut misaligned = vec![0u8; 256 * 1024];
        misaligned[0x21000 + 3..0x21000 + 7].copy_from_slice(&[0x0c, 0xb1, 0xba, 0x00]);
        assert!(!detect_zfs(&misaligned));
    }

    #[test]
    fn detect_zfs_does_not_panic_on_short_slices() {
        // Slices shorter than the ring start (0x20000) simply yield false.
        assert!(!detect_zfs(&[]));
        assert!(!detect_zfs(&[0u8; 8]));
        assert!(!detect_zfs(&vec![0u8; 0x20000]));
        // A slice reaching just one byte past a slot boundary but not a full
        // 4-byte magic must not panic and must return false.
        assert!(!detect_zfs(&vec![0u8; 0x20001]));
    }

    // The tier-2 real-artifact assertion (OpenZFS `zol-0.6.1` vdev label, env-gated
    // by `ZFS_LABEL_FIXTURE`) lives in `crates/core/tests/zfs_label_oracle.rs`,
    // alongside the other env-gated oracle tests, so `--lib` coverage stays exact.

    #[test]
    fn short_slice_does_not_panic() {
        // A slice shorter than a deep magic's offset must simply not match.
        assert_eq!(detect_name(&[0u8; 8]), None);
    }

    #[test]
    fn signatures_are_well_formed() {
        for s in FILESYSTEM_SIGNATURES {
            assert!(!s.magic.is_empty(), "{} has empty magic", s.name);
            assert!(!s.name.is_empty());
        }
    }

    #[test]
    fn fskind_as_str_is_canonical_lowercase() {
        assert_eq!(FsKind::XFS.as_str(), "xfs");
        assert_eq!(FsKind::HFS_PLUS.as_str(), "hfsplus");
        assert_eq!(FsKind::ISO9660.as_str(), "iso9660");
    }

    #[test]
    fn fskind_from_name_round_trips_every_const() {
        for &k in FsKind::known() {
            assert_eq!(FsKind::from_name(k.as_str()), k);
        }
    }

    #[test]
    fn fskind_known_has_every_const_and_no_duplicate_name() {
        let expected = [
            FsKind::NTFS,
            FsKind::FAT,
            FsKind::EXFAT,
            FsKind::EXT,
            FsKind::XFS,
            FsKind::APFS,
            FsKind::HFS_PLUS,
            FsKind::ISO9660,
            FsKind::UDF,
            FsKind::BTRFS,
            FsKind::ZFS,
            FsKind::UFS,
            FsKind::REFS,
            FsKind::ZIP,
            FsKind::AD1,
            FsKind::DAR,
        ];
        for k in expected {
            assert!(FsKind::known().contains(&k), "known() missing {k}");
        }
        assert_eq!(FsKind::known().len(), expected.len());
        let mut names: Vec<&str> = FsKind::known().iter().map(FsKind::as_str).collect();
        let total = names.len();
        names.sort_unstable();
        names.dedup();
        assert_eq!(names.len(), total, "duplicate as_str in known()");
    }

    #[test]
    fn fskind_display_writes_as_str() {
        assert_eq!(FsKind::BTRFS.to_string(), "btrfs");
        assert_eq!(format!("{}", FsKind::ZFS), "zfs");
    }

    #[test]
    fn fskind_debug_is_readable() {
        let s = format!("{:?}", FsKind::APFS);
        assert!(s.contains("FsKind"), "{s}");
        assert!(s.contains("apfs"), "{s}");
    }

    #[cfg(feature = "serde")]
    #[test]
    fn fskind_serde_round_trips_bare_string() {
        assert_eq!(serde_json::to_string(&FsKind::BTRFS).unwrap(), "\"btrfs\"");
        let back: FsKind = serde_json::from_str("\"btrfs\"").unwrap();
        assert_eq!(back, FsKind::BTRFS);
        for &k in FsKind::known() {
            let json = serde_json::to_string(&k).unwrap();
            assert_eq!(json, format!("\"{}\"", k.as_str()));
            assert_eq!(serde_json::from_str::<FsKind>(&json).unwrap(), k);
        }
    }

    #[cfg(feature = "serde")]
    #[test]
    fn fskind_deserialize_unknown_name_maps_to_sentinel() {
        let back: FsKind = serde_json::from_str("\"nonesuch-fs\"").unwrap();
        assert_eq!(back, FsKind::UNKNOWN_FALLBACK);
    }
}