udf-forensic 0.2.0

Forensic-grade, from-scratch UDF (ECMA-167 / OSTA) reader and tamper analyzer — volume recognition, partition maps (physical/virtual/sparable/metadata), File Entry and directory (FID) traversal, file data, plus graded findings (tag CRC/checksum, orphan File Entries, slack, file-after-volume) over any Read + Seek source
Documentation
# `udf-forensic` test fixtures

Per-file provenance for this crate's test data. The fleet-wide machine index is
[`issen/docs/corpus-catalog.md`](https://github.com/SecurityRonin/issen) — this README is
the co-located human detail; cross-reference, never duplicate. The tier rationale and the
list of independent-oracle validations live in [`docs/validation.md`](../../docs/validation.md);
this file documents only the per-fixture provenance.

## Status: real `mkudffs` corpus committed, reconciled against the `udfinfo` oracle

Three real UDF images, authored by **`mkudffs` (udftools 2.3)** and committed here, back the
`real_media_tests` in `src/lib.rs`. Each image's ground truth is cross-checked against the
independent **`udfinfo`** decoder (a separate codebase from this crate) — the oracle output is
captured verbatim below. The images are mostly-zero (≈10 KB each when packed by git), so they are
committed despite the global `*.img` ignore (a `.gitignore` negation un-ignores these three).

The corpus was minted on macOS via a rootless Linux container (`podman run ubuntu:24.04`) because
`mkudffs`/`udfinfo` are Linux-only; the verbatim commands are below and reproduce byte-identically
on any udftools 2.3 host.

- **Tool:** `udftools 2.3` (`2.3-1build2`, arm64), package `udftools`.
- **Mint command (per image):** `apt-get install -y udftools` then the `dd` + `mkudffs` lines below.
- **Redistribution:** `mkudffs` output is freely redistributable (the images contain only tool-authored
  filesystem structure and zero user data).

#### udf_vat.img — UDF 1.50, cdr media, Virtual (VAT) partition map

- **Source:** a UDF 1.50 image authored by `mkudffs` for CD-R media. The logical volume carries a
  physical partition map plus a Type-2 `*UDF Virtual Partition` (VAT) map.
- **Generator command (verbatim):**
  ```sh
  dd if=/dev/zero of=udf_vat.img bs=1M count=8
  mkudffs --media-type=cdr --udfrev=0x0150 udf_vat.img
  ```
- **Size / MD5:** 8 388 608 bytes - `1258d2b17f095af79bdb1141059eac84`
- **Consumed by:** `src/lib.rs` `mod real_media_tests` -> `vat_image_classified_virtual`
  (asserts `UdfPartitionKind::Virtual`) and `vat_image_matches_udfinfo_oracle`
  (asserts `partition_start == 257`, `partition_map_count == 2`).
- **`udfinfo` oracle output (independent ground truth):**
  ```
  udfinfo: Error: Virtual Allocation Table not found, maybe wrong --vatblock?
  udfinfo: Warning: Logical Volume is in inconsistent state
  filename=udf_vat.img
  label=LinuxUDF
  blocksize=2048
  blocks=4096
  numfiles=0
  numdirs=1
  udfrev=1.50
  udfwriterev=1.50
  integrity=opened
  accesstype=writeonce
  start=16, blocks=3, type=VRS
  start=96, blocks=16, type=MVDS
  start=128, blocks=1, type=LVID
  start=240, blocks=16, type=RVDS
  start=256, blocks=1, type=ANCHOR
  start=257, blocks=3839, type=PSPACE
  ```
  The VAT-not-found / inconsistent-state notes are expected for freshly-built write-once media (the
  LVID is left "opened" until the disc is closed); the partition space (`PSPACE`) starts at block 257,
  which this crate independently resolves as `partition_start = 257`.

#### udf_spar.img — UDF 2.01, dvdrw media, Sparable partition map

- **Source:** a UDF 2.01 image authored by `mkudffs` for DVD-RW media. The logical volume carries a
  single Type-2 `*UDF Sparable Partition` map; the image contains a sparing-space (`SSPACE`) region.
- **Generator command (verbatim):**
  ```sh
  dd if=/dev/zero of=udf_spar.img bs=1M count=8
  mkudffs --media-type=dvdrw --udfrev=0x0201 udf_spar.img
  ```
- **Size / MD5:** 8 388 608 bytes - `70285bf8979a026380517bfc48ae6ee6`
- **Consumed by:** `src/lib.rs` `mod real_media_tests` -> `sparable_image_classified_sparable`
  (asserts `UdfPartitionKind::Sparable`) and `sparable_image_matches_udfinfo_oracle`
  (asserts `partition_start == 1296`, `partition_map_count == 1`).
- **`udfinfo` oracle output (independent ground truth):**
  ```
  filename=udf_spar.img
  label=LinuxUDF
  blocksize=2048
  blocks=4096
  numfiles=0
  numdirs=1
  udfrev=2.01
  udfwriterev=2.01
  integrity=closed
  accesstype=overwritable
  start=16, blocks=3, type=VRS
  start=96, blocks=16, type=MVDS
  start=112, blocks=1, type=STABLE
  start=128, blocks=1, type=LVID
  start=256, blocks=1, type=ANCHOR
  start=272, blocks=1024, type=SSPACE
  start=1296, blocks=2528, type=PSPACE
  start=3839, blocks=1, type=ANCHOR
  start=3936, blocks=16, type=RVDS
  start=4080, blocks=1, type=STABLE
  start=4095, blocks=1, type=ANCHOR
  ```
  The 1024-block `SSPACE` (sparing space) sits before the partition space, so `PSPACE` starts at
  block 1296 — which this crate independently resolves as `partition_start = 1296`.

#### udf_plain.img — UDF 2.01, hd media, 512-byte blocks

- **Source:** a UDF 2.01 image authored by `mkudffs` for hard-disk media. Plain physical partition,
  **512-byte block size**.
- **Generator command (verbatim):**
  ```sh
  dd if=/dev/zero of=udf_plain.img bs=1M count=8
  mkudffs --media-type=hd --udfrev=0x0201 udf_plain.img
  ```
- **Size / MD5:** 8 388 608 bytes - `31d06a9942f8bc4983617631a9ac4e30`
- **Consumed by:** `real_media_tests::plain_512_block_image_parses_via_detected_block_size`. This is the
  512-byte-block oracle case: the crate detects the block size from the AVDP location (the anchor is at
  byte `256 × 512`, not `256 × 2048`) and resolves a **physical** partition at `partition_start = 257`
  with one map. Reconciled against `udfinfo` (`blocksize=512`, `udfrev=2.01`, `start=257, blocks=15864,
  type=PSPACE`). Together with the 2048-byte `vat`/`spar` images it exercises block-size detection across
  two media sector sizes.

## Reproducing the corpus on a non-Linux host

`mkudffs`/`udfinfo` are Linux-only. On macOS, mint via a rootless Linux container (no VM SSH needed):

```sh
mkdir -p ~/udfwork && cd ~/udfwork           # must live under /Users (podman machine mount)
podman machine start                          # one-time
podman run --rm -v "$PWD:/work:Z" ubuntu:24.04 bash -c '
  apt-get update -qq && apt-get install -y -qq udftools && cd /work &&
  dd if=/dev/zero of=udf_vat.img  bs=1M count=8 && mkudffs --media-type=cdr   --udfrev=0x0150 udf_vat.img  &&
  dd if=/dev/zero of=udf_spar.img bs=1M count=8 && mkudffs --media-type=dvdrw --udfrev=0x0201 udf_spar.img &&
  dd if=/dev/zero of=udf_plain.img bs=1M count=8 && mkudffs --media-type=hd   --udfrev=0x0201 udf_plain.img &&
  udfinfo udf_vat.img; udfinfo udf_spar.img; udfinfo udf_plain.img'
```

Then verify the MD5s match the values above before relying on the images. The matching entries in
[`issen/docs/corpus-catalog.md`](https://github.com/SecurityRonin/issen) classify these `SYNTHETIC`
(self-minted from a real third-party tool) and record the same verbatim commands.