Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/heimdall

crates/heimdall-wasm/wit/heimdall.wit

// SPDX-License-Identifier: AGPL-3.0-only
//
// Heimdall is a defensive sandbox. This is the whole interface between the
// Heimdall wasm host and a guest (spec §6): bytes in, a verdict and files out.
// There is no WASI: no filesystem, no network, no clock, no environment.

package heimdall:guest@0.1.0;

world job {
  /// One job parameter, passed through from the caller's Job params.
  record param {
    key: string,
    value: string,
  }

  /// The guest accepted the input. `note` is a short human-readable summary.
  record output {
    note: string,
  }

  /// The guest refused the input. `reason` says why, in plain words.
  record refusal {
    reason: string,
  }

  /// Hand one output file to the host. `name` is a single plain file name
  /// (ASCII letters, digits, '.', '_', '-', '+'; no '/', no leading '.').
  /// A refused name or a limit hit stops the guest; the job is then refused.
  import emit-file: func(name: string, bytes: list<u8>);

  /// Run the job on `input`.
  export run: func(input: list<u8>, params: list<param>) -> result<output, refusal>;
}

/// The DECODER half of a split guest (docs/GATLING-WASM.md). The host runs
/// many short-lived instances of it, each with its own memory, fuel and
/// deadline, each handed only the compressed frames of its own chunks. It
/// imports nothing at all, not even `emit-file`: bytes in, bytes out.
world decoder {
  /// One compressed chunk: the whole frame, and the decoded size the
  /// archive index declares (the decoder must produce exactly that many).
  record chunk-ref {
    frame: list<u8>,
    size: u64,
    /// false: only the digest is wanted and `bytes` comes back empty.
    want-bytes: bool,
  }

  /// What one chunk decoded to: the BLAKE3-256 of the decoded bytes (32
  /// bytes) and, when they were asked for, the bytes.
  record decoded {
    digest: list<u8>,
    bytes: list<u8>,
  }

  /// One answer per chunk, in order: the decoded chunk, or why its frame
  /// does not decode.
  type chunk-result = result<decoded, string>;

  export decode-chunks: func(chunks: list<chunk-ref>) -> list<chunk-result>;
}

/// The VERIFIER half of a split guest: a `job` that never decodes itself. It
/// hands compressed frames to the host, which decodes them in isolated
/// `decoder` instances (in parallel), checks the length and BLAKE3 of every
/// answer natively, and returns the answers in order. The verifier never
/// shares memory with a decoder.
world verifier {
  include job;

  record chunk-ref {
    frame: list<u8>,
    size: u64,
    want-bytes: bool,
  }

  record decoded {
    digest: list<u8>,
    bytes: list<u8>,
  }

  type chunk-result = result<decoded, string>;

  /// Decode `chunks` in decoder instances. A decoder that traps, runs out of
  /// its own limits or answers falsely ends the job with the host's code.
  import decode-chunks: func(chunks: list<chunk-ref>) -> list<chunk-result>;

  /// SHA-256 and BLAKE3-256 (32 bytes each) of a blob.
  record member-digest {
    sha256: list<u8>,
    blake3: list<u8>,
  }

  /// The digests of every blob, in order, computed by the host natively and
  /// in parallel (the host is the code that signs the verdict; it hashes,
  /// it never parses). The blobs are lent, not copied.
  import digest-members: func(blobs: list<list<u8>>) -> list<member-digest>;
}