Introduction

Daku is an asynchronous host interface abstraction API for WebAssembly plugins, drivers, applications, and more! Daku is both an API and a file format. Ardaku is an engine you can use for running Daku modules on different Wasm runtimes.

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

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

Goals

  • Simple
  • Efficient
  • Modular
  • Minimal (in API surface, and memory footprint)
  • Asynchronous
  • Backwards Compatible
  • Reduced context switching
  • Security-first
  • First-class multimedia portals (WASI compatible)

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

Channel 0

Channel 0 is a special channel that allows you to send arbitrary bytes (Buffer) to the host from the guest. This is what a custom plugin API would use. Sending commands on channel 0 may also open "Device Channels".

Portal Channels

Portal channels represent a bus (and permission to talk to it) on the computer. Portal channels are opened before the program starts (based on the daku custom section). Going in order of the portals, will allocate channels in ascending order starting from 1. There may be more than one channel per portal. They can not and will not be closed until the WASM module exits.

Device Channels

Device channels represent something (physically or virtually) connected to a bus. Device channels are opened by sending commands to portals (this command is not allowed to fail if constructed properly, even if the data is stale - so you may open a channel to a disconnected device). They are allocated after the portal channels. When the device channel is closed it's number goes into a garbage list. The last ID is popped off the garbage list when opening a new device channel (if it exists).

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

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

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 mem
  • Export "main" function as run
  • Export ready list global pointer as rl4 for a size 16 ready list
    • rl0 for a size 1 ready list
    • rl1 for a size 2 ready list
    • rl2 for a size 4 ready list
    • rl3 for a size 8 ready list
    • rl4 for a size 16 ready list
    • rl5 for a size 32 ready list
    • rl6 for a size 64 ready list
    • rl7 for a size 128 ready list
    • rl8 for a size 256 ready list
    • rl9 for a size 512 ready list

Type: ReadyList[N]

The list of ready/complete commands

Fields

  • ready_list: [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

Lang

Type: Lang

Spoken language

Fields

  • identifier: [byte; 2]

Language Identifiers

Region

Type: Region

Region code

Fields

  • identifier: [byte; 2]

Region Identifiers

LangRegion

Type: LangRegion

A spoken language plus a region code for dialect

Fields

  • lang: Lang The language
  • region: Region The region to specify dialect

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.

0x00 - Log

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

Portal Channels

  1. Error Log Level
  2. Warn Log Level
  3. Info Log Level
  4. Debug Log Level
  5. Trace Log Level

Readiness

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

Command: Log

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.

Fields

  • target: Text Target name
  • message: Text Message to print

Traps

  1. If message is not valid UTF-8, or contains a NUL byte
  2. If target is not valid UTF-8, or contains a NUL byte
  3. If address at message.addr + message.size - 1 has no page
  4. If address at target.addr + target.size - 1 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.

Portal Channels

  1. Developer prompt

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).

Portal Channels

  1. Initiate HTTPS request

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).

Portal Channels

  1. Accept HTTPS clients

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 🧪