Introduction

Daku is an asynchronous host interface abstraction API for WebAssembly plugins, drivers, applications, and more! It is developed as a supporting specification for the Ardaku project (Ardaku is an engine for running Daku modules for the listed use-cases).

Daku Specification v1.0.0-pre.0 (draft v11)

The current version of Daku targets the full WebAssembly 2.0 spec without any non-standard or experimental features.

Goals

  • Modular
  • Minimal (in API surface, and memory footprint)
  • Asynchronous
  • Stable base API
  • As simple and efficient as possible
  • Reduced context switching
  • Security-first
  • First-class multimedia portals
  • Portals compatible with WASI versions via 2-way abstractions

Glossary

Guest

A program that interfaces with the Daku API.

Host

The program that embeds the guest.

Command

A command is a data structure that is sent from the guest to the host. All commands execute once and complete asynchronously. Any command may be canceled.

Channel

Channels receieve commands from the guest. For the guest to receive data, the guest must send a memory address of a buffer to the host; The buffer then gets overwritten upon completion of the command.

Portal

Portals are channels that get opened before execution of the guest WebAssembly module begins. A custom command portal is always opened at channel 0. Other portals can be opened using the portal extension, which provides system-interface APIs similar to what WASI does.

Daku Custom Section

The Daku custom section is a WebAssembly custom section that must follow the order of conventional sections

  1. name
  2. producers
  3. target_features
  4. daku

The base section without extensions is just a WebAssembly vector of portal IDs.

For details on the experimental nucleide extension see https://docs.rs/nucleide/latest/nucleide/#daku-daku

Channels

Allocation

Channels are implicitly allocated. Channel 0 is always the custom portal channel that can send arbitrary messages to the host. The first portal listed in the daku WebAssembly custom section will reserve channel 1. All portal channels stay open for the lifetime of the guest.

Additional channels called device channels can be both opened and closed. They must be opened from a portal. When they are closed, they go to a "garbage list". Once a new device channel is opened, the last closed channel id is reüsed for the new channel. Channels are otherwise opened in consecutive ascending order.

Channel 0

A channel 0 command is sent as a Buffer.

Commands

Commands are data sent from the guest to the host. Their structure is specific to the channel which they are sent. All commands have a lifetime, and the data sent should only be freed after the command lifetime has ended.

Command Lifetime Flow

  • Guest allocates a command on the heap
  • Guest sends the command address to the host with ar()
  • Host returns from ar() with the address of the command in the "ready list"
  • Host frees or reüses (for streams) the command as its lifetime has ended

Type: Command

Commands are the way the Daku application sends messages to the environment.

Fields

  • addr: opt[T] Pointer to command data for command T
  • chan: int Channel to send data on

Host Exports

The exported WebAssembly API contains a single function:

Function: ar()

Retconned from "Ardaku"; Asynchronous Request function.

Returns once at least one command (with a non-zero ready value) completes. An early return can be forced with a null noöp command.

(import "daku" "ar" (func $event
    (param $cmd_size i32)   ;; List[Command].size
    (param $cmd_addr i32)   ;; List[Command].addr
    (result i32)            ;; List[Uint32].size 
))

Parameters

  • $cmd_size The size of the data pointed to by $cmd_addr
  • $cmd_addr A pointer in the wasm memory to a list of $cmd_size commands

Returns

  • The number of ready commands in the ready list (new length of ready list).

Guest Exports

  • Export 32-bit memory as memory
  • Export "main" function as run
  • Export Daku global pointer as 16 for a size 16 ready list

Type: Daku[N]

Commands are the way the Daku application sends messages to the environment.

Fields

  • command: [opt[T]; N] Ready list of commands

Types

Daku defines a few shared types that can be used in the command definitions. Occasionally, a command should use a custom type that packs data better, but it should match similarly to another type defined here.

Primitives

  • val An arbitrary untyped 32 bits
  • int A 32-bit integer
  • num A 32-bit floating point (May require all 1 bits for NaN representation)

  • long A 64-bit integer
  • half A 16-bit integer
  • byte An 8-bit integer
  • nybl A 4-bit integer

  • ptr[T] A 32-bit address
  • opt[T] A 32-bit address or Null (0)

List

Type: List[T]

A list of elements of type T

Fields

  • size: int Number of elements pointed to at addr
  • addr: ptr[T] Packed list of size elements

Buffer

Type: Buffer

A list of bytes

Fields

  • data: List[byte] Buffer data

Text

Type: Text (subtype of List)

A buffer of UTF-8 text.

Fields

  • text: Buffer UTF-8 formatted buffer

Vector

Type: Vector

A 4-dimensional vector.

Fields

  • x: num X position (-left +right)
  • y: num Y position (-up +down)
  • z: num Z position (-back +front)
  • w: num W position (0 = vector, 1 = position)

Positions

Type: Positions (subtype of List)

A list of audio channel physical positions relative to the user at the origin.

The position must either be a unit vector (length of 1) or all zeros (center).

Fields

  • list: List[Vector] - List of speakers / microphones in configuration.
    • x: num - X position (-left +right)
    • y: num - Y position (-up +down)
    • z: num - Z position (-back +front)
    • w: num - LFE? (0 = No, NaN = Yes)

Audio

Type: Audio

A buffer of floating-point audio.

Fields

  • size: val
    • frame: byte - Number of channels per frame.
    • chunk: byte - Number of frames per chunk.
    • count: half - Number of chunks.
  • addr: ptr[float] - Interleaved samples; List of size.frame * size.chunk * size.count 32-bit floats.
  • rate: int - Sample rate of the audio (hertz)
  • config: opt[Positions] - Custom channel position configuration, default is FLAC/SMPTE/ITU-R recommendations.

FLAC/SMPTE/ITU-R recommendations

  • 1 Channel: Mono (Mono)
    1. (0, 0, 0, 0)
  • 2 Channels: Stereo (Left, Right)
    1. (-1, 0, 0, 0)
    2. (1, 0, 0, 0)
  • 3 Channels: Surround 3.0 (Left, Right, Center)
    1. (-1, 0, 0, 0)
    2. (1, 0, 0, 0)
    3. (0, 0, 0, 0)
  • 4 Channels: Surround 4.0 (FrontL, FrontR, SurroundL, SurroundR)
    1. (-0.5, 0, 0.8660254037844387, 0)
    2. (0.5, 0, 0.8660254037844387, 0)
    3. (-0.9396926207859084, 0, -0.3420201433256687, 0)
    4. (0.9396926207859084, 0, -0.3420201433256687, 0)
  • 5 Channels: Surround 5.0 (FrontL, FrontR, Front, SurroundL, SurroundR)
    1. (-0.5, 0, 0.8660254037844387, 0)
    2. (0.5, 0, 0.8660254037844387, 0)
    3. (0, 0, 1, 0)
    4. (-0.9396926207859084, 0, -0.3420201433256687, 0)
    5. (0.9396926207859084, 0, -0.3420201433256687, 0)
  • 6 Channels: Surround 5.1 (FrontL, FrontR, Front, Lfe, SurroundL, SurroundR)
    1. (-0.5, 0, 0.8660254037844387, 0)
    2. (0.5, 0, 0.8660254037844387, 0)
    3. (0, 0, 1, 0)
    4. (0, 0, 0, 1)
    5. (-0.9396926207859084, 0, -0.3420201433256687, 0)
    6. (0.9396926207859084, 0, -0.3420201433256687, 0)
  • 7 Channels: Surround 6.1 (FrontL, FrontR, Front, Lfe, Back, Left, Right)
    1. (-0.5, 0, 0.8660254037844387, 0)
    2. (0.5, 0, 0.8660254037844387, 0)
    3. (0, 0, 1, 0)
    4. (0, 0, 0, 1)
    5. (0, 0, -1, 0)
    6. (-1, 0, 0, 0)
    7. (1, 0, 0, 0)
  • 8 Channels: Surround 7.1 (FrontL, FrontR, Front, Lfe, BackL, BackR, Left, Right)
    1. (-0.5, 0, 0.8660254037844387, 0)
    2. (0.5, 0, 0.8660254037844387, 0)
    3. (0, 0, 1, 0)
    4. (0, 0, 0, 1)
    5. (-0.5, 0, -0.8660254037844387, 0)
    6. (0.5, 0, -0.8660254037844387, 0)
    7. (-1, 0, 0, 0)
    8. (1, 0, 0, 0)

Dimensions

Type: Dimensions

A 2-D integer width/height pair.

Fields

  • size: val Dimensions pair
    • width: half Width in pixels
    • height: half Height in pixels

Raster

Type: Raster

A buffer of pixels (image, picture, video, etc.).

Fields

  • size: Dimensions Size of the image
  • addr: ptr List of size.width * size.height pixels by row according to format.
  • format: int Bits per component
    • 8 8 bits (integer)
    • 16 16 bits (integer)
    • 32 32 bits (float)
  • colorspace: int
    • 0 Mask (1 component - grayscale/alphascale)
    • 1 RGBA (4 components)
    • 2 BGRA (4 components)

Timestamp

Type: Timestamp

The number of TAI microseconds since Jan 1 00:00:00.000_000, year 0 in ISO 8601:2004

This gives about a range of ±292_470 years since year 0.

This differs from Unix time in 3 ways:

  • Epoch is 0000-01-01T00:00:00.000_000 TAI instead of 1970-01-01T00:00:00.000_000 UTC
  • Precision is microseconds instead of seconds
  • TAI not UTC, meaning that a leap second gets representation, rather than being represented as repeat of previous second as it would be in unix time.

Fields

  • micros: long

Date

Type: Date

A naïve date (unspecified timezone)

Fields

  • year: half Range: 0 ~ 65_535
  • month: byte Range: 1 ~ 12
  • day: byte Range: (of week: 1 ~ 7) << 5 | (of month: 1 ~ 31)

Time

Type: Time

A naïve time (unspecified timezone)

Fields

  • hour: byte Range: 0 ~ 23
  • minute: byte Range: 1 ~ 59
  • millis: half Range: 0 ~ 60_999 (can represent leap seconds)

DateTime 🧪

Type: DateTime

A naïve date and time (unspecified timezone)

Fields

  • date: Date The date
  • time: Time The time

Portals

Portals allow Daku applications to interface with the hardware and environment / OS. The environment is not required to grant access to all portals requested by the application, and may either stop the application or mock out a fake implementation to protect the user's privacy.

Implementations of portals should strive to be "as mathematical" as possible, meaning that there's no fancy engineering abstractions - just sending and receiving data and defining the required functionality. This is to reduce the risk for possibly needing to deprecating portals in the future. That said, it's ok to pack smaller pieces of data into one value if 32-bits can be guaranteed to most likely never be needed.

Portals should also make use of shared high-level types.

Kinds

Some portals only have one command that is sent on the channel to that portal. But, there are some portals that can create new channels that accept different commands and notify on different events.

0x00 - Log

Log a message with an associated target and log level, usually to help with debugging.

If no target is necessary, prefer empty target for traditional stdout/stderr compatibility. Treat I/D/T as stdout, and W/E/F as stderr, preferring I and W.

Readiness

Becomes ready once logging has completed (stopping the process after ready wouldn't result in a partially-formed log message).

Will not log if log level is not provided. This is useful for using readiness to flush the logs, since logs are guaranteed to be ordered.

Command: Log

Fields

  • message: Text - Message to print; first character is log level
    • F: Fail (Trap the task)
    • E: Error
    • W: Warn
    • I: Info
    • D: Debug
    • T: Trace
  • target: Text Target name

Traps

  1. If log level character is invalid
  2. If message is not valid UTF-8, or contains a NUL byte
  3. If target is not valid UTF-8, or contains a NUL byte
  4. If address at message.addr + message.size has no page
  5. If address at target.addr + target.size has no page

0x01 - Prompt

Receive a line of textual user input from a debugging console.

The text appended to the buffer won't contain the newline character.

Readiness

Becomes ready once a line of text has been entered. With either

  1. Buffer is not big enough, with capacity modified to what is required
  2. Buffer is big enough, command text overwritten

Command: Prompt

Read textual user input from some source.

Fields

  • capacity: ptr[int] - (In/Out) Pointer to capacity of command.
  • command: ptr[Text] - (In/Out) Pointer to user-sent line of input.

Traps

  1. If input capacity is less than command.size
  2. If address at (input) command.addr + capacity has no page
  3. If address at capacity + 3 has no page

0x02 - Fetch 🧪

Do an HTTPS request to specified URL.

SSE is expected to be implemented as an abstraction over this API (rather than be provided as its own portal).

Connection

Host device channel is opened upon readiness (allocated only if successful). The channel allocation should happen immediately upon return of ar(), and must follow in the order of the ready list.

Readiness

Becomes ready once either

  • The resource host can't be reached (network error), capacity set to 0.
  • The resource host hung up, capacity set to 0.
  • The resource host has sent back a chunk of the resource (a new device channel has been opened)
    1. Buffer is not big enough, buffer overwritten up to capacity bytes, and capacity modified how many more bytes are required
    2. Buffer is big enough, buffer overwritten, capacity unchanged.

Command: Fetch

Fields

  • url: Text - URL to do an HTTPS request to (does not include https:// protocol)
  • headers: Text - Extra headers to send (separated by \n)
  • body: opt[List[byte]] - Optional payload/content body to send
  • method: int - 0: GET, 1: HEAD, 2: POST, 3: PUT, 4: DELETE.
  • capacity: ptr[int] - (In/Out) Pointer to capacity of buffer.
  • buffer: ptr[List[byte]] - (In/Out) Pointer to buffer for receiving parts of the HTTP response.

Traps

  1. If url does not start with one of
    • Domain name lowercase (ranged a~z or 0~9, - and . allowed after first character, but not consecutively, and not last character before termination character - one of :/?)
    • IPv4 Address - 4 integers ranged 0:255 each separated by .)
    • Ipv6 Address - [, then 8 lowercase hexaxecimal numbers from 1 to 4 digits separated by :, and ]. A grouping of consecutive zero numbers can be replaced with ::, rather than requiring all 8 numbers.
  2. If url after domain/IP doesn't either start with one of :/? or end
  3. If url sections :/? are out of order
  4. If url port after : is not in range 0~65535
  5. If url path after / is not a~z, A~Z, -, _, %, ., ~, +, or /
  6. If url query after ? is not a~z, A~Z, 0~9, -, _ ., ~, +, =, ;, or &
  7. If headers begins with \n or ends with \n
  8. If headers line does not match Title-Kebab-Case: expected-type (no \r allowed)
  9. If headers line contains an invalid (Like Not-A-Header), redudant (Like Content-Length), or insecure (Also like Content-Length) header
  10. If input capacity is less than buffer.size
  11. If address at (input) buffer.addr + body.capacity has no page
  12. If address at body.addr + body.size has no page
  13. If address at url.addr + url.size has no page
  14. If address at headers.addr + headers.size has no page
  15. If address at body + 3 has no page
  16. If address at capacity + 3 has no page
  17. If address at buffer + 3 has no page

Device: Host 🧪

Channel representing an HTTPS connection to a server.

Type: FetchError

An error may be indicated once either Fetch or Host command becomes ready.

To indicate an error, capacity will be set to 0. buffer.size will be set to an error code:

Variants (int)

  1. Network - Server unreachable (network error).
  2. Hangup - Server hung up.

Readiness

Becomes ready once either

  • The resource host can't be reached (network error), capacity set to 0.
  • The resource host hung up, capacity set to 0.
  • The resource host has sent back a chunk of the resource (a new device channel has been opened)
    1. Buffer is not big enough, buffer overwritten up to capacity bytes, and capacity modified how many more bytes are required
    2. Buffer is big enough, buffer overwritten, capacity unchanged.

Command: Host

Variants (int)

  1. Poll - Poll for more data from server.
  2. Hangup - Hang up connection to server.

Traps

  1. If variant is unknown (not 0 or 1)

0x03 - Serve 🧪

Serve resources over HTTPS.

SSE is expected to be implemented as an abstraction over this API (rather than be provided as its own portal).

Connection

Client device channel is opened upon readiness. The channel allocation should happen immediately upon return of ar(), and must follow in the order of the ready list.

Readiness

Becomes ready once either

  • A client connects.
  • The number of connection errors (with errors being non-null) since the last successful connetion, in one of the 4 categories has exceeded 255

Command: Serve

Fields

  • config: val
    • port: half Port number to connect to
    • connections: half How many client connections to attempt maximum
      • If sign bit is set to negative, allow other computers to connection as a client
  • errors: opt[val] Output number of errors that occured since last successful connection (or since start, if becoming ready on first success)
    • load: byte Number rejected based on load being too high (clients connecting is larger than ready list size)
    • maximum: byte Number rejected based on too many connections exceeding maximum set with connections
    • https: byte Number rejected for invalid HTTPS
    • timeout: byte Number rejected based on timeout on sending headers

Traps

  1. If connections = -32768
  2. If port = 0 (local only) and connections sign bit is negative
  3. If address at errors + 3 has no page

Device: Client 🧪

Channel representing an HTTPS connection to a client.

Type: Poll

Variants (int)

  1. Continue - Keep polling.
  2. Hangup - Hang up connection.
  • Respond with informational response status code 100-199 (FIXME: full list)
  • Respond with successful response status code 200-299 (FIXME: full list)
  • Respond with redirection message status code 300-399 (FIXME: full list)
  • Respond with client error response status code 400-499 (FIXME: full list)
  • Respond with server error response status code 500-599 (FIXME: full list)

Readiness

Becomes ready once either

  • The client has headers and possible content body ready to be retrieved (no error is set to 0).
  • The client has headers, but they are too large for request (request.capacity set to required capacity)
  • Errors have overflown (at least one error set to 255)
  • The client hung up (poll set to Hangup)
  • The client is ready for more data (poll set to Continue)

Command: Client

Fields

  • content: List[byte] Content body to send.
  • poll: opt[Poll] (In/Out) Pointer to poll state (In:Server, Out:Client)
  • request: opt[_] Request buffer
    • capacity: ptr[int] (In/Out) Pointer to capacity of headers_content.
    • headers_content: ptr[Text] (In/Out) Pointer to headers and content.

Traps

  1. If poll invalid/unknown variant
  2. If poll is not 0 or 1 after response headers already sent
  3. If poll is non-null and not a reponse status code before headers sent
  4. If request.size > request.capacity
  5. If address at content.addr + content.size has no page
  6. If address at error + 3 has no page
  7. If address at request + 7 has no page
  8. If address at request.capacity + 3 has no page
  9. If address at request.headers_content.addr + request.capacity has no page

0x04 - Speakers 🧪

0x05 - Microphone 🧪

0x06 - Screen 🧪

0x07 - Camera 🧪

0x08 - Window 🧪

0x09 - Spawn 🧪

0x0A - User 🧪

0x0B - Preferences 🧪

0x0C - System 🧪

0x0D - About 🧪

0x0E - File 🧪

0x0F - Hid 🧪

0x10 - Timer 🧪

0x11 - Clock 🧪

0x12 - Gpu 🧪

0x13 - Location 🧪