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
name
producers
target_features
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 commandT
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 bitsint
A 32-bit integernum
A 32-bit floating point (May require all 1 bits for NaN representation)
long
A 64-bit integerhalf
A 16-bit integerbyte
An 8-bit integernybl
A 4-bit integer
ptr[T]
A 32-bit addressopt[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 ataddr
addr: ptr[T]
Packed list ofsize
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 ofsize.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)
(0, 0, 0, 0)
- 2 Channels: Stereo (Left, Right)
(-1, 0, 0, 0)
(1, 0, 0, 0)
- 3 Channels: Surround 3.0 (Left, Right, Center)
(-1, 0, 0, 0)
(1, 0, 0, 0)
(0, 0, 0, 0)
- 4 Channels: Surround 4.0 (FrontL, FrontR, SurroundL, SurroundR)
(-0.5, 0, 0.8660254037844387, 0)
(0.5, 0, 0.8660254037844387, 0)
(-0.9396926207859084, 0, -0.3420201433256687, 0)
(0.9396926207859084, 0, -0.3420201433256687, 0)
- 5 Channels: Surround 5.0 (FrontL, FrontR, Front, SurroundL, SurroundR)
(-0.5, 0, 0.8660254037844387, 0)
(0.5, 0, 0.8660254037844387, 0)
(0, 0, 1, 0)
(-0.9396926207859084, 0, -0.3420201433256687, 0)
(0.9396926207859084, 0, -0.3420201433256687, 0)
- 6 Channels: Surround 5.1 (FrontL, FrontR, Front, Lfe, SurroundL, SurroundR)
(-0.5, 0, 0.8660254037844387, 0)
(0.5, 0, 0.8660254037844387, 0)
(0, 0, 1, 0)
(0, 0, 0, 1)
(-0.9396926207859084, 0, -0.3420201433256687, 0)
(0.9396926207859084, 0, -0.3420201433256687, 0)
- 7 Channels: Surround 6.1 (FrontL, FrontR, Front, Lfe, Back, Left, Right)
(-0.5, 0, 0.8660254037844387, 0)
(0.5, 0, 0.8660254037844387, 0)
(0, 0, 1, 0)
(0, 0, 0, 1)
(0, 0, -1, 0)
(-1, 0, 0, 0)
(1, 0, 0, 0)
- 8 Channels: Surround 7.1 (FrontL, FrontR, Front, Lfe, BackL, BackR, Left, Right)
(-0.5, 0, 0.8660254037844387, 0)
(0.5, 0, 0.8660254037844387, 0)
(0, 0, 1, 0)
(0, 0, 0, 1)
(-0.5, 0, -0.8660254037844387, 0)
(0.5, 0, -0.8660254037844387, 0)
(-1, 0, 0, 0)
(1, 0, 0, 0)
Dimensions
Type: Dimensions
A 2-D integer width/height pair.
Fields
size: val
Dimensions pairwidth: half
Width in pixelsheight: half
Height in pixels
Raster
Type: Raster
A buffer of pixels (image, picture, video, etc.).
Fields
size: Dimensions
Size of the imageaddr: ptr
List ofsize.width
*size.height
pixels by row according to format.format: int
Bits per component8
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 of1970-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_535month: byte
Range: 1 ~ 12day: 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 ~ 23minute: byte
Range: 1 ~ 59millis: half
Range: 0 ~ 60_999 (can represent leap seconds)
DateTime 🧪
Type: DateTime
A naïve date and time (unspecified timezone)
Fields
date: Date
The datetime: 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 levelF
: Fail (Trap the task)E
: ErrorW
: WarnI
: InfoD
: DebugT
: Trace
target: Text
Target name
Traps
- If log level character is invalid
- If
message
is not valid UTF-8, or contains a NUL byte - If
target
is not valid UTF-8, or contains a NUL byte - If address at
message.addr + message.size
has no page - 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
- Buffer is not big enough, with
capacity
modified to what is required - 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 ofcommand
.command: ptr[Text]
- (In/Out) Pointer to user-sent line of input.
Traps
- If input
capacity
is less thancommand.size
- If address at (input)
command.addr + capacity
has no page - 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)
- Buffer is not big enough,
buffer
overwritten up tocapacity
bytes, andcapacity
modified how many more bytes are required - Buffer is big enough,
buffer
overwritten,capacity
unchanged.
- Buffer is not big enough,
Command: Fetch
Fields
url: Text
- URL to do an HTTPS request to (does not includehttps://
protocol)headers: Text
- Extra headers to send (separated by\n
)body: opt[List[byte]]
- Optional payload/content body to sendmethod: int
- 0: GET, 1: HEAD, 2: POST, 3: PUT, 4: DELETE.capacity: ptr[int]
- (In/Out) Pointer to capacity ofbuffer
.buffer: ptr[List[byte]]
- (In/Out) Pointer to buffer for receiving parts of the HTTP response.
Traps
- 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.
- Domain name lowercase (ranged a~z or 0~9,
- If
url
after domain/IP doesn't either start with one of:/?
or end - If
url
sections:/?
are out of order - If
url
port after:
is not in range 0~65535 - If
url
path after/
is nota~z
,A~Z
,-
,_
,%
,.
,~
,+
, or/
- If
url
query after?
is nota~z
,A~Z
,0~9
,-
,_
.
,~
,+
,=
,;
, or&
- If
headers
begins with\n
or ends with\n
- If
headers
line does not matchTitle-Kebab-Case: expected-type
(no\r
allowed) - If
headers
line contains an invalid (LikeNot-A-Header
), redudant (LikeContent-Length
), or insecure (Also likeContent-Length
)header
- If input
capacity
is less thanbuffer.size
- If address at (input)
buffer.addr + body.capacity
has no page - If address at
body.addr + body.size
has no page - If address at
url.addr + url.size
has no page - If address at
headers.addr + headers.size
has no page - If address at
body + 3
has no page - If address at
capacity + 3
has no page - 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
)
Network
- Server unreachable (network error).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)
- Buffer is not big enough,
buffer
overwritten up tocapacity
bytes, andcapacity
modified how many more bytes are required - Buffer is big enough,
buffer
overwritten,capacity
unchanged.
- Buffer is not big enough,
Command: Host
Variants (int
)
Poll
- Poll for more data from server.Hangup
- Hang up connection to server.
Traps
- If variant is unknown (not
0
or1
)
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 toconnections: 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 withconnections
https: byte
Number rejected for invalid HTTPStimeout: byte
Number rejected based on timeout on sending headers
Traps
- If
connections = -32768
- If
port = 0
(local only) andconnections
sign bit is negative - If address at
errors + 3
has no page
Device: Client 🧪
Channel representing an HTTPS connection to a client.
Type: Poll
Variants (int
)
Continue
- Keep polling.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 toHangup
) - The client is ready for more data (
poll
set toContinue
)
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 buffercapacity: ptr[int]
(In/Out) Pointer to capacity ofheaders_content
.headers_content: ptr[Text]
(In/Out) Pointer to headers and content.
Traps
- If
poll
invalid/unknown variant - If
poll
is not0
or1
after response headers already sent - If
poll
is non-null and not a reponse status code before headers sent - If
request.size > request.capacity
- If address at
content.addr + content.size
has no page - If address at
error + 3
has no page - If address at
request + 7
has no page - If address at
request.capacity + 3
has no page - If address at
request.headers_content.addr + request.capacity
has no page