Skip to content

Architecture

forensic-vfs defines five layer-open traits, each a two-step probe() (recognize) then open() (peel), plus one orchestrator, SourceOpen (in forensic-vfs-resolver). The diagram lays the layers out in their normal nesting order — an archive wraps a container, which holds a partition table, which may hold an encrypted volume, which holds a filesystem. But SourceOpen is a graph, not a fixed pipeline: every non-terminal peel yields an ImageSource that re-enters resolution, so unusual real-evidence stacks (raw → LUKS → LVM → ext4, an archive inside a filesystem, encryption that is container metadata) resolve by re-entry, in any order.

flowchart TD
    SRC(["Evidence — a path or bytes"])
    SRC --> IS
    IS["<b>ImageSource</b> — read-only positioned bytes<br/>the one edge every layer consumes, and each peel re-produces"]
    IS --> A

    A["<b>1 · ArchiveOpen</b> — peel packaging<br/>gz · bz2 · tar · zip · clbx · 7z · AD1 · DAR"]
    C["<b>2 · ContainerOpen</b> — decode the acquisition image<br/>E01 · VMDK · VHD · VHDX · QCOW2 · DMG · AFF4 · raw"]
    V["<b>3 · VolumeSystemOpen</b> — walk the partition table<br/>MBR · GPT · APM"]
    E["<b>4 · EncryptionOpen</b> — unlock the volume<br/>BitLocker · LUKS · FileVault · VeraCrypt"]
    F["<b>5 · FileSystemOpen</b> — mount the filesystem<br/>NTFS · FAT · ext4 · XFS · btrfs · APFS · HFS+ · UFS · ISO9660 · UDF"]
    A --> C --> V --> E --> F
    F --> FN["<b>FsNode tree</b> — files · data streams · MACB metadata"]

    A -. "Members → N members re-enter" .-> IS
    C -. "peeled source re-enters" .-> IS
    E -. "decrypted source re-enters" .-> IS

Example nestings a graph handles that a fixed lane would not: E01 → GPT → BitLocker → NTFS (encryption after volume), raw → LUKS → LVM → ext4 (encryption before volume), E01 → APFS-container(encrypted volume) → APFS (encryption is container metadata, not a separate step).

The byte source

ImageSource is a positioned-read, Send + Sync trait with read_at(&self, …) and no write method. This delivers three properties at once:

  1. Parallel reads&self shares one Arc<dyn ImageSource> across workers; a Read + Seek cursor's &mut self cannot.
  2. Read-only by construction — no write API exists to misuse.
  3. Clean dyn compositionSend + Sync are auto traits, so Arc<dyn ImageSource> is itself Send + Sync.

Adapters bridge the existing world: FileSource (positioned OS reads, no Mutex<File>), SubRange (a byte window that is itself an ImageSource), and SourceCursor (a Read + Seek view for legacy call sites).

Identity and metadata

  • FileId is filesystem-specific (NtfsRef{entry,seq}, ExtInode{ino,gen}, ApfsOid{oid,xid}, FatDirEntry, IsoExtent, Opaque), so a reused slot is never confused with the original.
  • FsMeta carries the name/metadata allocation split (Allocated | Deleted | Orphan), per-timestamp TimeSource + TimeResolution (including NTFS's 100 ns WinFileTime), ADS/resource-fork stream info, and residency — without the eager run-list (runs come from extents() lazily).

Locator

A recursive chain of Layer nodes. Identity is the structured enum, so raw path bytes containing a delimiter can never collide two specs. Two text forms: a lossless canonical URI (round-trip is a test- and fuzz-enforced invariant) and a lossy human Display. Credentials are supplied out-of-band at resolve time, never stored in the address.

Crate structure

Three roles across three crates (ADR 0007):

Crate Role Status
forensic-vfs byte source (ImageSource), the five *Open traits, Openers dispatch table, Locator, FsMeta, FsKind published (0.4) — this crate
forensic-vfs-resolver the SourceOpen orchestrator — impl SourceOpen for Openers, recursive graph descent, walk, snapshot_view published (0.1) — this workspace
forensic-vfs-engine default_openers() wiring the ~17 concrete readers + Vfs::open(path) host bootstrap + concurrent block cache a separate published repo
disk-forensic / disk4n6 thin CLI over the engine evolving

The leaf carries the contracts and nothing that names a concrete format; the resolver carries the reader-independent descent policy; the engine (its own repo, so 17 reader trees never pollute the leaf's CI or audit surface) carries the concrete wiring. The division and its rationale — including why the resolver was extracted from the leaf once the archive layer forced richer selection policy — are recorded in ADR 0007; the layer model in ADR 0003; first-class archives in ADR 0008.

Frontier

The horizontal layers are strong — container and filesystem/archive readers implement their contracts in production. The two vertical layers are the remaining work: the VolumeSystemOpen (MBR/GPT/APM/VSS) and EncryptionOpen (BitLocker/LUKS/FileVault) readers exist as crates but are not yet wired to the contract. See PRD §7 for the ranked coverage matrix and remaining work.