Technique interfaces

Techniques covers what each technique does on disk: byte layouts, capacity, and detection. This page covers only the code shape.

type Technique interface {
    Name() string
    Hide(filesystem.Entry, Request) (Result, error)
    Detect(filesystem.Entry, Request) ([]Finding, error)
    Clear(filesystem.Entry, Request) (Result, error)
    Extract(filesystem.Entry, Request) ([]byte, error)
}

One interface, three techniques

There is one Technique interface, not one per capability.

Each concrete technique takes a bare filesystem.Entry and checks for the one capability it needs: NamedStreamCapable, SlackSpaceCapable, or TimestompCapable.

If the check fails, it returns the ErrUnsupported sentinel, wrapped as technique %q: unsupported on this filesystem. Match it with errors.Is, not by string.

Get(name) maps a --technique string to the concrete technique.

Request is the shared input. Most of its fields are optional, and an operation reads only the ones it needs:

Slack framing

New payloads use a 28-byte NEM2 header with a random per-hide nonce. Older 12-byte NEMO frames stay readable.

The header tells a payload apart from leftover noise. The nonce stops identical payloads from reusing stale backup digests. Frame parsing checks the versioned header and payload bounds before slicing. The code is in slackframe.go.

The exact byte layout is in nemo’s payload frame.

Restoration and backup contract

For slack-space operations, Request.Backup func(Backup) error is called before any destructive write, when it is set. It receives the pre-write state: Backup{Technique, Target, Location, Original []byte, Timestamp}. If it returns an error, the operation aborts.

Persistence

The saving side is in manifest.go:

The file is nemo-manifest.jsonl by default, and --manifest moves it. It is JSON Lines with one Backup per line. Original is base64 and Timestamp is RFC 3339.

See slack-space hiding for what gets recorded.

Extract

extract reads a hidden payload without changing anything.

First-frame limitation

A target’s slack space can hold more than one valid frame. Stale frames from an earlier, uncleared hide are one cause.

Extract returns only the first frame. Detect lists them all. So the two can disagree about which payload is the payload. Clear also uses the first frame, so at least extract and clear agree.

There is no --index flag to pick a specific frame.

Timestomp limitation

filesystem.TimestompCapable exposes Timestamp, SupportsTimestamp, and SetTimestamp.

Reading a current value does not show whether it was altered. There is no historical baseline and no anomaly detector, so timestomp.Detect returns no findings.

timestomp.Hide does not save the original timestamp to the manifest. timestomp.Clear needs the caller to supply the original, and it rejects a zero value. See Timestomping.

Finding and Result

Finding and Result are shared value types that do not depend on the technique:

type Finding struct {
    Technique string // "named-stream" | "slack-space" | "timestomp"
    Location  string // stream name, slack region offset range, or timestamp field
    Size      int64  // bytes of hidden data recovered; 0 for timestomp findings
}

type Result struct {
    Technique string
    Target    string // path of the entry acted on
    Detail    string // stream name written, slack offset range, or "field=value" for timestomp
    Bytes     int64  // bytes written/removed; 0 for timestomp
}

Finding is what detect prints, one per line (technique, location, size). It matches the output spec in the CLI reference.

Result is what hide and clear return on success. It carries no hash or timestamp, because internal/custody already captures both for every WriteAt at the Image layer. See Image and custody.

The command builds the custody-log line from Result (which technique, which target, what happened) plus the hash and timestamp the custody decorator recorded. Neither layer duplicates the other.

Why commands stay simple

Commands depend only on these interfaces, never on a filesystem-specific technique type.

Hide, Detect, and Clear only touch the capability interfaces. So the technique logic works on any filesystem. There is one implementation per technique, all in internal/technique: namedStreamTechnique, slackSpaceTechnique, and timestompTechnique. Each works with any Entry that has the capability it needs.