diff --git a/assets/fonts/planned.txt b/assets/fonts/planned.txt index 5b9e8e19..90bc16e3 100644 --- a/assets/fonts/planned.txt +++ b/assets/fonts/planned.txt @@ -7,3 +7,5 @@ planned: https://font.gohu.org/ https://terminus-font.sourceforge.net/ https://int10h.org/oldschool-pc-fonts/ + https://en.wikipedia.org/wiki/Hershey_fonts + \ No newline at end of file diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index bb7f0261..fdfe3e13 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -11,8 +11,58 @@ enum SystemResource : usize } /// All syscalls related to generic resource management. +/// +/// - Resources are created through various calls in the kernel api, but their +/// lifetime and availability is managed through calls inside this namespace. +/// - After creation, a resource is strongly bound to the process that created the +/// resource. +/// - When a resource is destroyed, it becomes unusable from userland. +/// - As long as a resource is strongly bound to at least a single process, it is +/// not automatically destroyed. +/// - As soon as a resource has no strong bindings anymore, it is destroyed by the kernel. +/// - A process can only access the resources bound to the process. +/// - In addition to strong bindings, weak bindings also exist. +/// - Weak bindings allow a process access to a resource, but don't keep the resource alive. +/// - This allows processes to access resources they don't own. +/// - Resources can be tethered to other resources. +/// - If a tethered resource is destroyed, the associated resource is also destroyed. +/// +/// NOTE: Every kernel object the userland can interact with is a resource. +/// +/// NOTE: If a resource is destroyed by any means (zero strong bindings, manual destruction, tethering), +/// it destroys all resources tethered to it (i.e. where it is the `source`). +/// This may lead to a cascade called "tether chain". +/// +/// NOTE: Tethering can form cycles. This allows resources to be tightly bound together and if one +/// resource dies, the other one also dies. +/// +/// NOTE: The order in which tether destruction is executed is unspecified. Implementors must not assume +/// any order of destruction. +/// +/// NOTE: Tethering does not affect resource lifetimes. A tether will never keep +/// other resources alive. Tethers are removed if the `target` resource is destroyed +/// and all of its tether effects are resolved. +/// +/// LORE: Originally, Ashet OS had no concept of bindings, but only of ownership. +/// But this quickly lead to problems like "the desktop server also owns the window +/// so even if the application releases the window, it is not destroyed". +/// Cycles like this could only be resolved by an explicit call to `destroy` instead +/// of `release`. But this yields brittle code that expects correct application shutdown. +/// In cases of crashes, the resource would only be released, but not destroyed. +/// The idea of allowing a process to access a resource, but not keeping it alive solves +/// this problem completely and also allows some other patterns to work well. +/// +/// LORE: The idea of resource tethering came after the idea of bindings. +/// Tethering allows resolving a problem most other operating systems have, which is: +/// What happens if a thread dies unexpectedly. +/// In operating systems without tethering, the application has to monitor the threads +/// and if a thread dies, it has to manually clean up the resources of that thread +/// assuming it has properly registered the resources in a global management structure. +/// With tethering, we can tether the lifetime of a file handle to the lifetime of its +/// owning thread, meaning: If the thread dies, the file is closed. +/// As this is a very useful property, i've decided to implement it as a broader general +/// concept instead of tying it to threads only. namespace resources { - /// Returns the type of the system resource. syscall get_type { in @"resource": SystemResource; @@ -20,242 +70,1069 @@ namespace resources { error InvalidHandle; } - /// Returns the current owner of this resource. - syscall get_owners { - in @"resource": SystemResource; - in owners: ?[]Process; - out count: usize; + /// Immediately destroys the resource and releases its memory. + /// + /// NOTE: This will *always* destroy the resource, even if it's + /// still strongly bound by a process. + /// + /// NOTE: This immediately triggers tether chains and destroys + /// all tethered resources as well. + /// + /// NOTE: `destroy` always succeeds; destroying an invalid or already-destroyed handle is a no-op. + syscall destroy { + in @"resource": SystemResource; + } + + /// Defines the possible kinds of bind operations we have. + enum BindOperation : u8 + { + /// The resource shall be unbound from the process. + item unbind = 0; + + /// The resource will be bound strongly to the process. + item strong = 1; + + /// The resource will be bound weakly to the process. + item weak = 2; + + /// This operation ensures that the binding is at least `weak`, + /// but may never be downgraded from `strong`. + /// + /// This gives the ability to ensure a process can definitely access a resource + /// without force-downgrading it to a `weak` binding if the process already has + /// a `strong` binding. + item at_least_weak = 3; } - /// Adds the process to the owners of this resource, so the process - /// can safely access it without fear of having a use-after-free. - syscall send_to_process { + /// Binds a resource to a process. + /// + /// The success of this operation allows `target` to access `resource`, and optionally + /// gain/lose a strong binding of the resource. + /// + /// NOTE: This function can be used to up/downgrade the binding of a resource. + /// + /// NOTE: If `binding` is `unbind`, the resource is instead unbound from the process and + /// may be released. + /// + /// If all strong bindings of a resource are removed, the resource will be destroyed. + /// + /// NOTE: The following operations are idempotent: + /// - `binding=weak` when already bound weak. + /// - `binding=strong` when already bound strong. + /// - `binding=unbind` when not bound. + /// - `binding=at_least_weak` when already bound weak or strong. + syscall bind { in @"resource": SystemResource; - in target: Process; - error DeadProcess; + + /// The process which the resource should be bound to. If `null`, uses the current process. + in target: ?process.Process; + + /// The type of binding operation that shall be performed. + in binding: BindOperation; + + /// The resource or process handle was invalid. error InvalidHandle; + + /// The `target` process is dead, but still has an alive handle. + /// NOTE: Cannot happen when `binding` is `unbind`. + error ZombieProcess; + + /// The system ran out of resources when handling the request. + /// NOTE: Cannot happen when `binding` is `unbind`. error SystemResources; } - /// Drops the ownership of the resource for the current process. - /// If no owner remains, the resource will be destroyed and it's - /// memory will be released. - /// The handle must be assumed invalid for this process after - /// this function returns. - syscall release { - in @"resource": SystemResource; + + /// Defines the possible kinds of binding a resource can have. + enum Binding : u8 + { + /// The resource is not bound to the process. + /// This means the process cannot access the resource and also does not keep + /// the resource alive. + item unbound = 0; + + /// The resource is strongly bound to the process. + /// As long as a single strong binding exists, the resource is + /// valid. + item strong = 1; + + /// The resource is weakly bound to the process. + /// This means the process can access the resource, but does not + /// keep the resource alive. + item weak = 2; + } + + /// Returns the binding for a resource on a process. + syscall get_binding { + in @"resource": SystemResource; + + /// The process for which the resource binding shall be queried. If `null`, uses the current process. + in target: ?process.Process; + + /// The kind of binding the resource has on `target`. + out binding: Binding; + + /// The resource or process handle was invalid. + error InvalidHandle; } - /// Immediatly destroys the resource and releases its memory. + /// Returns the current bindings of a resource. /// - /// NOTE: This will *always* destroy the resource, even if it's - /// also owned by another process. - syscall destroy { - in @"resource": SystemResource; + /// NOTE: The order in which processes and bindings are returned are not guaranteed to be + /// stable between two calls. + /// + /// NOTE: When `processes` is `null`, `bindings` can be used to count strong vs weak bindings. + /// + /// NOTE: When `resource` is the currently executing process, that process is always returned + /// in `processes`. + syscall get_bindings { + /// The resource which should be queried. + in @"resource": SystemResource; + + /// If not `null`, will receive the process handles that have a binding + /// on `resource`. + /// + /// NOTE: The process handles will be bound to the calling process with `BindOperation.at_least_weak` + /// to ensure resource access. + in processes: ?[]process.Process; + + /// If not `null`, will receive the binding types of all bindings on `resource`. + /// If `processes` is also provided, `bindings[i]` corresponds to `processes[i]`. + /// Otherwise, the order is unspecified. + /// + /// NOTE: No value written in this will ever be `unbound`. + in bindings: ?[]Binding; + + /// The number of bindings for the resource if `processes` and `bindings` is `null`. + /// Otherwise the number of elements written to `processes` and/or `bindings`. + /// + /// NOTE: If `processes` and `bindings` have different lengths, `min(processes.len, bindings.len)` is chosen. + out count: usize; + + /// The resource or process handle was invalid. + error InvalidHandle; + + /// The system ran out of resources when handling the request. + error SystemResources; + } + + /// Defines the possible ways of how a resource is tethered to another resource. + enum TetherMode : u8 + { + /// The resources are not tethered. + item untethered = 0; + + /// If the source resource is destroyed, the target resource will be destroyed as well. + item strong = 1; + } + + /// Binds the lifetime of the 'target' resource to the lifetime of the 'source' resource. + /// + /// If `mode` is `strong` and the `source` resource is destroyed, the `target` resource + /// will implicitly be destroyed as well. + syscall tether { + /// The resource which destruction will trigger the tether event. + in source: SystemResource; + + /// The resource that will receive the tether event. + in target: SystemResource; + + /// The kind of tethering performed. + in mode: TetherMode; + + /// One of the resource handles was invalid. + error InvalidHandle; + + /// The system ran out of resources when handling the request. + error SystemResources; + } + + /// Queries the tethering between two resources. + syscall get_tether { + /// The resource which destruction will trigger the tether event. + in source: SystemResource; + + /// The resource that will receive the tether event. + in target: SystemResource; + + /// The kind of tethering performed between the two resources. + out mode: TetherMode; + + /// One of the resource handles was invalid. + error InvalidHandle; + } + + //? TODO: Potentially define get_tethers? + + /// An anchor is a system resource without any properties or functions + /// besides the basic properties of resources. + /// + /// Anchors can be used as groups for tether objects or as tokens passed + /// between processes. + resource Anchor { } + + /// Creates a new `Anchor` resource. + syscall create_anchor { + /// The newly created anchor. + out anchor: Anchor; + + /// The system ran out of resources when handling the request. + error SystemResources; } } +/// This namespace is related to asynchronous running (sys)calls, which +/// is the heart of the operating system I/O. +/// +/// All system calls in Ashet OS are non-blocking except `process.thread.yield`, +/// `overlapped.await_any` and `overlapped.await_any_of`. +/// This means that all regular system calls will return as soon as possible +/// without ever waiting on external events or other operations. +/// +/// But as each operating system requires slow/blocking operations, Ashet OS +/// provides a single way of handling long-running operations: +/// +/// The Asynchronously Running system Call (ARC). +/// +/// Each ARC represents an operation that might not complete immediately, and must +/// be scheduled to the kernel. +/// Later on, the ARC is returned from the kernel in an await operation or cancelled. +/// +/// NOTE: Overlapped operations that are cancelled before completion return `error.Cancelled`. +/// +/// NOTE: Completion may also be observed via `cancel` returning `error.Completed`. +/// +/// NOTE: This concept is typically called *completion queue*. +/// +/// NOTE: The `ARC` structure must always be embedded in the associated structure type +/// for `ARC.type`, as the kernel will cast the pointer to the `ARC` structure +/// to the associated structure type. +/// +/// NOTE: Between `schedule` and the await or cancel of the ARC, the userland +/// must keep the scheduled `ARC` object and the struct containing valid and unchanged. +/// +/// The kernel will modify the `output` and `error` fields of the ARC enclosing structure. +/// +/// NOTE: If the owning process is terminated, all scheduled ARCs of that process are implicitly +/// cancelled (best-effort) and removed from the kernel; they will not be returned to userland. +/// +/// NOTE: Completion delivery is exactly-once. +/// If `cancel` returns `Completed`, the operation will not be returned by `await_any` / `await_any_of`. +/// If an operation was returned by an await syscall, later `cancel` will return `Unscheduled`. +/// +/// NOTE: If an overlapped operation has a system resource as an input, and the system resource is destroyed +/// during the operation, the operation is cancelled. namespace overlapped { - /// Handle to an asynchronously running (system) call. + /// + /// NOTE: This struct must always be embedded in the associated + /// structure for `ARC.type` at the offset 0. + /// + /// The kernel will derive the actual structure type from `ARC.type` + /// and will cast a pointer to an `ARC` into the actual call structure type. struct ARC { /// The type of operation that is performed. + /// + /// NOTE: This field is never changed by the kernel. field type: Type; - /// A user-specified pointer-sized field which - field tag: usize; + /// A user-specified array of pointer-sized fields which may + /// contain userland information associated with the ARC. + /// + /// NOTE: This is primarily meant for event loop systems so + /// they can associate their own data structures with + /// ARCs returned from `await_any`. + /// + /// NOTE: This field is never changed by the kernel. + field tag: [3]usize; typedef Type = <>; } - /// Starts new asynchronous operations. + /// Starts a new asynchronous operation. /// /// NOTE: Until the operation has successfully completed or was /// cancelled, the ARC structure must be considered owned /// by the kernel and must not be changed from userspace. - /// It can also change its contents spuriously until the - /// operation is returned to userland. /// + /// The kernel may modify the enclosing structure's `output` and `error` fields + /// while scheduled. The kernel never modifies `ARC.type` and `ARC.tag`. + /// + /// NOTE: When scheduling an ARC, the kernel associates the calling + /// thread with the operation, so the awaiter can choose whether + /// to await only its own thread's ARCs or not. syscall schedule { - in @"async_call": *ARC; + /// The call that should be scheduled. + in @"arc": *ARC; + + /// Returned when the kernel already has an active async operation + /// with the same address. error AlreadyScheduled; + + //? TODO: Consider "InProgress" or "Conflict" as a new error which + //? would be returned when an ARC cannot be scheduled due to conflicts + //? instead of going through the whole overlapped dance just to return + //? error.InProgress. + error SystemResources; } - /// Awaits one or more scheduled asynchronous operations and returns the + /// Cancels an asynchronous call. + /// + /// NOTE: Cancellation leaves the operation in an undefined state. This means + /// that cancellation isn't necessarily atomic and writes may have been + /// performed halfway, nearly completely or not at all. + /// + /// Example: A tiled renderer may have completed all draw commands for + /// half of the picture tiles when being cancelled. + /// In comparison, a linear renderer would have performed + /// half of the commands on the whole picture. + /// + /// NOTE: If the operation has already completed, an error will be returned. + /// + /// NOTE: A cancelled operation cannot be awaited anymore. + /// + /// NOTE: On success or the `Completed` error, the operation will not + /// be owned by the kernel anymore and cannot be awaited anymore by + /// `await_any` or `await_any_of`. + syscall cancel { + /// The operation to cancel. + in arc: *ARC; + + /// Returned when the ARC has already run to completion. + /// + /// NOTE: This means the operation was not cancelled because it already completed + /// with or without an error. Userland should handle this case gracefully. + /// + /// NOTE: If this error is returned, the operation will not be owned by the kernel anymore + /// and shall be treated as if it was returned from `await_any` or `await_any_of` as + /// completed. + error Completed; + + /// The kernel does not know the `arc` operation. + error Unscheduled; + } + + enum Thread_Affinity : u8 { + /// Waits for ARCs scheduled from *any* thread in the current process. + item all_threads = 0; + + /// Waits for ARCs scheduled from *this* thread. + item this_thread = 1; + } + + enum BlockMode : u8 { + /// Don't wait for any additional calls to complete, just return + /// whatever was completed in the meantime. + /// + /// NOTE: This mode does NOT suspend or yield the current thread + /// and keeps the active time slice. So spinning with this mode + /// without other yield points will lock up the system! + item dont_block = 0; + + /// Wait for at least a single call to complete operation. + item wait_one = 1; + + /// Wait until all scheduled operations have completed. + /// + /// This will only wait so long until either + /// a) all scheduled ops are stored into the result array + /// or + /// b) the result array is full + /// + /// NOTE: If `thread_affinity` is `.all_threads`, other threads can still + /// schedule more operations and make this function block longer. + item wait_all = 2; + } + + /// Awaits some scheduled asynchronous operations and returns the /// number of `completed` elements. /// - /// The kernel will fill out `completed` up to the returned number of elements. - /// All other values are undefined. + /// The kernel will fill `completed` up to the returned number of elements. + /// All other values are left untouched. /// /// NOTE: For blocking operations, this function will suspend the current - /// thread until the request has been completed. + /// thread until the request has been completed. This means that other + /// threads can continue their work. /// - /// RELATES: @ref await_completion_of - syscall await_completion { + /// NOTE: If `completed.len` is zero, the operation will never suspend and + /// immediately return zero for `completed_count`. + /// + /// NOTE: The completed ARCs are not owned by the kernel anymore and may be scheduled again. + syscall await_any { + /// A caller-provided array which will be filled with pointers to + /// the completed ARCs. + /// NOTE: Kernel will only touch the first `completed_count` elements and + /// keeps the rest unchanged. in completed: []*ARC; - in options: Await_Options; + + /// Defines how the operation will suspend. + in block_mode: BlockMode; + + /// Defines the thread affinity for the await option. + /// This allows awaiting ARCs that were scheduled by the calling thread + /// and ignoring all others. + in thread_affinity: Thread_Affinity; + + /// The number of elements filled into `completed` by the kernel. out completed_count: usize; - error Unscheduled; } - /// Awaits one or more explictic asynchronous operations and returns the - /// number of `events` elements. + /// Awaits one or more ARCs from a set and returns the number of completed operations. /// /// The kernel will only await elements provided in `events` and all of those events must - /// not be awaited by another `await_completion_of`. + /// not be awaited by another `await_any_of`. /// - /// When the function returns, `events` will have all completed events unchanged, and all - /// unfinished events set to `null`. This way, a simple check via index can be done instead of - /// the need for iteration of `events` to find what was finished. + /// When the call returns, the kernel will have partitioned events into two parts: + /// - The head (`events[0..completed_count]`) elements will be completed. + /// - The tail (`events[completed_count..]`) elements are still in progress. + /// + /// This allows the caller to invoke the syscall again later with the tail part of the + /// array in order to await the rest. /// /// NOTE: This syscall will always return as soon as a single event has finished. /// - /// NOTE: It is invalid to await the same operation with two concurrent calls to `await_completion_of`. + /// NOTE: It is invalid to await the same operation with two concurrent calls to `await_any_of`. /// /// NOTE: Elements awaited with this function will be guaranteed to not be returned by - /// another concurrent call to `await_completion`. + /// a concurrent call to `await_any`. /// - /// NOTE: For blocking operations, this function will suspend the current - /// thread until the request has been completed. + /// NOTE: This function will suspend the current thread and yield to other threads + /// if, and only if none of the events are completed. /// - /// RELATES: @ref await_completion - syscall await_completion_of { - in events: []?*ARC; - error InvalidOperation; - error Unscheduled; + /// NOTE: The order in which the elements in `events` will be partitioned by the kernel + /// is implementation-defined and must not be assumed to have any meaningful order. + /// + /// NOTE: The completed ARCs are not owned by the kernel anymore and may be scheduled again. + syscall await_any_of { + /// A pre-filled list of events that shall be awaited. + /// On completion of the call, this list will be reordered by the kernel into + /// two halves, the first `completed_count` containing the completed events. + in events: []*ARC; + + /// The number of ARCs completed inside `events`. out completed_count: usize; - } - /// Cancels an asynchronous call. - /// - /// NOTE: If the operation has already completed, an error will be returned saying so. - /// - /// NOTE: The cancelled operation will not be returned by `await` anymore. - syscall cancel { - in aop: *ARC; - error Completed; + /// Another `await_any_of` already awaits an event from `events`. + error InvalidOperation; + + /// The kernel does not know an operation inside `events`. + /// When this error is returned, no ARCs will be removed from the completion queue + /// and `events` is left unmodified. error Unscheduled; } - } -/// Syscalls related to processes +/// Syscalls related to processes, threads and execution control. namespace process { - - enum ExitCode : u32 { - item success = 0; - item failure = 1; - ... - item killed = 0xFFFFFFFF; + /// A process is first and foremost a context for resource resolution. + /// + /// In addition to that, each process has an additional initial thread + /// that is created and launched with `Spawn`. + /// + /// Resources in threads are always resolved in regard to their owning + /// process. + /// + /// A process has two states: + /// - Active: The process is loaded and not terminated yet. + /// - Zombie: The process resource still exists, but the process itself is already terminated. + /// + /// When a process is terminated or killed, all associated threads are exited, and all bound resources + /// are unbound. This may trigger the destruction of these resources. + /// + /// In addition to that, a termination reason is stored informing observers of the process + /// how the process was terminated. + /// + /// If a process terminates itself, it can provide a hint if it was successfully terminated ("clean exit") + /// or if it failed ("error during execution"). + /// + /// A process becomes Zombie on termination and remains until its `Process` resource is destroyed. + /// Destroying the Process resource reaps the zombie and frees remaining bookkeeping. + /// + /// NOTE: Destroying an active process resource will terminate the process and immediately reap the + /// zombie. + /// + /// NOTE: A process may be of two kinds: + /// - regular + /// - daemon + /// A daemon process may have zero associated foreground threads, but still be active. If it has + /// zero threads total, it can keep resources alive but cannot execute code until a new thread is + /// spawned into it. + /// + /// NOTE: A regular process is terminated when its last foreground thread is exited. + /// If all foreground threads have exited without calling `terminate`, the process + /// is terminated with `TerminationReason.regular_exit` and `success = true`. + /// + /// LORE: Ashet OS only has a single boolean for communicating success or failure to the outside. + /// This was chosen as most applications in the wild either use `EXIT_SUCCESS (0)` or `EXIT_FAILURE (1)` + /// in C libraries anyways and don't use the exit code to communicate meaning. + /// If a more complex communication to the outside is required, there are better options like pipes or shared memory + /// passed into the process. + resource Process { } + + /// A thread is the base unit of execution. + /// + /// Each thread operates concurrently to the other threads + /// in a cooperative manner: + /// + /// This means that threads voluntarily yield execution to the + /// OS scheduler in order to let other threads do their work. + /// + /// NOTE: A thread is always associated with a process and syscalls + /// invoked by a thread resolve resources always in regard to + /// that process. + /// + /// LORE: In contrast to other operating systems, threads do not expose + /// a success/failure state. + /// This was chosen as applications can use internal signalling + /// to better communicate failure/success than having a single integer + /// for that. + resource Thread { } + + /// Terminates the current process and marks it as "controlled exit". + syscall terminate { + /// Provides the information if the process terminated successfully or not. + in success: bool; + noreturn; } - /// Returns a pointer to the file name of the process. - syscall get_file_name { - in target: ?Process; - out file_name: str; - error InvalidHandle; - } + /// Terminates a given process and marks it as "killed". + /// + /// NOTE: A killed process is never considered successful as it was terminated + /// from the outside. + /// + /// NOTE: If the current process is passed, this function will not return. + /// + /// NOTE: If the `target` process is already terminated, this operation is idempotent. + syscall kill { + /// The process that should be killed. + in target: Process; - /// Returns the base address of the process. - syscall get_base_address { - in target: ?Process; - out base_address: usize; + /// `target` is not a valid process resource. error InvalidHandle; } - /// Returns the arguments that were passed to this process in `Spawn`. - syscall get_arguments { - in target: ?Process; - in argv: ?[]SpawnProcessArg; - out count: usize; - error InvalidHandle; - } - /// Terminates the current process with the given exit code - syscall terminate { - in exit_code: ExitCode; - noreturn; - } + /// An argument passed to a process. + struct SpawnArg { + /// The associated name of the argument. + field name: str; - /// Terminates a foreign process. - /// If the current process is passed, this function will not return - syscall kill { - in target: Process; - error InvalidHandle; + /// The type of the argument. + field type: Type; + + /// The associated value for `type`. + field value: Value; + + enum Type : u8 { + /// The argument is a mere flag and carries no value. + /// Its existence itself already carries semantics. + item flag = 0; + + /// The argument has a string value associated. + /// NOTE: `Value.text` is active. + item string = 1; + + /// The argument has a resource value associated. + /// NOTE: The spawned process receives a strong binding for the passed resource. + /// NOTE: `Value.resource` is active. + item @"resource" = 2; + } + + union Value { + field text: String; + field @"resource": SystemResource; + } + + struct String { + field text: str; + } } - /// Spawns a new process + /// Spawns a new regular process. + /// + /// NOTE: The kernel will perform a copy of all strings inside `overlapped.schedule`, so it is safe to + /// reuse the string memory after the operation is successfully scheduled. + /// + /// This prevents unwanted use-after-free by the kernel. + /// + /// NOTE: `Spawn` will create a single initial thread, the main thread. This thread is a + /// foreground thread which will use the executables entry point as its thread function. async_call Spawn { /// Relative base directory for `path`. - in dir: Directory; + in dir: fs.Directory; + /// File name of the executable relative to `dir`. in path: str; + /// The arguments passed to the process. - /// If a `SystemResource` is passed, it will receive the created process as a owning process. - /// It is safe to release the resource in this process as soon as this operation returns. - in argv: []const SpawnProcessArg; + /// It is safe to release the resource binding to the current process as soon as this operation returns. + in arguments: []const SpawnArg; + /// Handle to the spawned process. out process: Process; + error BadExecutable; - error DiskError; + error IoError; error FileNotFound; error InvalidHandle; error InvalidPath; error SystemResources; } - namespace thread { - /// Returns control to the scheduler. Returns when the scheduler - /// schedules the process again. - syscall yield { - } + /// Creates a new, empty daemon process. + /// + /// NOTE: The created process will be of `ProcessKind.daemon`. + /// + /// NOTE: The kernel will not create a main thread for the process. + syscall create_empty_process { + /// The arguments passed to the process. + /// It is safe to release the resource binding to the current process as soon as this syscall returns. + /// + /// NOTE: The kernel copies all argument strings and stores them in the process object before returning, + /// so the caller may reuse/free argument memory immediately after the syscall returns. + in arguments: []const SpawnArg; - /// Terminates the current thread. - syscall exit { - in exit_code: ExitCode; - noreturn; - } + /// Size of the memory allocated for the process. + /// + /// NOTE: The memory allocated by the kernel will not have well-defined contents. + /// The kernel may zero the memory, or just assign it without change. + /// Userland must not assume contents of the memory without previously writing it. + /// + /// NOTE: This address for this memory will be returned by `get_base_address`. + /// + /// NOTE: The kernel may allocate more than the requested memory. Userland must assume + /// exactly `image_size` are valid and may not write beyond `image_size` + /// bytes after the address returned by `get_base_address`. + /// + /// NOTE: If `0` is passed, no memory will be allocated and `get_base_address` will return an error. + in image_size: usize; - /// Waits for the thread to exit and returns its return code. - syscall join { - in _param0: Thread; - out exit_code: ExitCode; - error InvalidHandle; - } + /// Handle to the created process. + out process: Process; - /// Defines the signature of a thread entry point. - /// The parameter is the `arg` value passed to `spawn`. - /// The return value is the exit code of the thread. - typedef ThreadFunction = fnptr (?anyptr) u32; + /// A handle in `arguments` is not a valid resource handle. + error InvalidHandle; - /// Spawns a new thread with `function` passing `arg` to it. - /// If `stack_size` is not 0, will create a stack with the given size. + error SystemResources; + } + + enum TerminationReason : u8 { + /// The process terminated properly through a call to `terminate`. + item regular_exit = 0; + + /// The process was killed with `kill`. + item killed = 1; + + /// The process was shut down by the kernel in order to protect the system + /// from a crash. + /// This may include execution of invalid instructions, division by zero or + /// other platform illegal behaviour. + item faulted = 2; + } + + /// Completes when the given process terminates. + /// + /// NOTE: The call will immediately complete if `target` is already terminated. + /// + /// NOTE: Awaiting termination of the same process multiple times is idempotent. + /// + /// NOTE: Multiple `WaitForTermination` operations can be scheduled at once and + /// will all complete when the process terminates. + async_call WaitForTermination { + in target: Process; + + /// The reason why the process was terminated. + out reason: TerminationReason; + + /// Contains the success of the process termination. + /// This value is: + /// - The value passed to `terminate.success`. + /// - True if terminated by all foreground threads exiting. + /// - `false` otherwise. + out successful: bool; + + /// `target` is not a valid process resource. + error InvalidHandle; + } + + /// Returns the arguments that were passed to this process in `Spawn`. + syscall get_arguments { + /// The process for which the arguments shall be returned. + /// If `null` is passed, the current process will be used. + in target: ?Process; + + /// A constant slice of the process' arguments. + /// + /// NOTE: The returned memory and all interior pointers are valid as long + /// as the `target` process resource is not destroyed. + /// + /// NOTE: If an argument refers to a `SystemResource`, the resource will be bound + /// to the calling process with an `at_least_weak` bind operation to ensure + /// resource access. + out argv: []const SpawnArg; + + /// `target` is not a valid process resource. + error InvalidHandle; + + error SystemResources; + } + + /// Returns a pointer to the file name of the process. + syscall get_file_name { + /// The process for which the file name shall be returned. + /// If `null` is passed, the current process will be used. + in target: ?Process; + + /// The file name of the process passed to `Spawn` or the empty string if no + /// file name exists. + /// + /// NOTE: This is only the basename of the file and not the full path as + /// the information about the path is not helpful without the associated + /// directory handle. + /// + /// NOTE: The returned memory and all interior pointers are valid as long + /// as the `target` process resource is not destroyed. + out file_name: str; + + /// `target` is not a valid process resource. + error InvalidHandle; + } + + /// Returns the base address of the process. + /// + /// This is the address at which the executable image is loaded and relocated to. + /// + /// NOTE: The memory is valid until the process is terminated. + syscall get_base_address { + /// The process for which the base address shall be returned. + /// If `null` is passed, the current process will be used. + in target: ?Process; + + /// The base address of the process. + out base_address: usize; + + /// `target` is not a valid process resource. + error InvalidHandle; + + /// `target` process has no assigned memory region. + error NoMemory; + } + + /// Enumeration of the different process kinds that exist. + enum ProcessKind : u8 { + /// A regular process is automatically terminated when all foreground threads have exited. + item regular = 0; + + /// A daemon process does not automatically exit when all foreground threads have exited. + /// It stays alive until it is explicitly terminated. + item daemon = 1; + } + + /// Changes the kind of a process. + /// + /// NOTE: If a process is changed to `ProcessKind.regular` and has no active foreground + /// threads, the process is automatically terminated and this function may not return. + syscall set_kind { + /// The process for which the kind shall be updated. + /// If `null` is passed, the current process will be used. + in target: ?Process; + + /// The new kind of the `target` process. + in new_kind: ProcessKind; + + /// `target` is not a valid process resource. + error InvalidHandle; + + /// The `target` process is dead, but still has an alive handle. + error ZombieProcess; + } + + /// Queries the kind of a process. + syscall get_kind { + /// The process for which the kind shall be returned. + /// If `null` is passed, the current process will be used. + in target: ?Process; + + /// The kind of the `target` process. + out kind: ProcessKind; + + /// `target` is not a valid process resource. + error InvalidHandle; + } + + namespace thread { + /// Returns control to the scheduler. Returns when the scheduler + /// schedules the process again. + syscall yield { + } + + /// Gets the resource handle for the currently executing thread. + syscall get_current { + /// The handle to the current thread. + /// NOTE: This handle is ensured to be at least weakly bound to the current process. + out handle: Thread; + } + + /// Gets the process for a given thread. + syscall get_process { + /// The handle for which the process shall be queried. + /// If `null`, will yield the process for the current thread. + in handle: ?Thread; + + /// The handle to the process owning `handle`. + /// NOTE: This handle is ensured to be at least weakly bound to the current process. + out proc: Process; + + /// `handle` is not a valid thread resource. + error InvalidHandle; + + /// The system ran out of resources when handling the request. + /// + /// NOTE: This error can only when `handle` is not `null`. + error SystemResources; + } + + /// Terminates the current thread without returning from the thread function. + /// + /// NOTE: This does not perform any stack unwinding and no code will be executed + /// after a call to this function. + /// + /// NOTE: Exiting a thread stops the execution, but it does not destroy or release the + /// thread resource. + syscall exit { + noreturn; + } + + /// Defines the signature of a thread entry point. + /// The parameter is the `arg` value passed to `spawn`. + typedef ThreadFunction = fnptr (?anyptr) void; + + /// Enumeration of the available thread kinds. + enum ThreadKind : u8 { + /// A foreground thread keeps a regular process alive. + /// + /// As long as a single foreground thread exists, a process is not + /// automatically terminated. + item foreground = 0; + + /// A background thread does not keep a regular process alive. + /// + /// This means that all background threads are automatically exited + /// when the owning process is terminated. + item background = 1; + } + + /// Spawns a new thread with `function` passing `arg` to it. + /// + /// NOTE: A spawned thread will always be associated with the current + /// process. syscall spawn { + /// The target process for which the thread shall be spawned. + /// If `null`, will use the current process. + /// + /// NOTE: Spawning a thread in a foreign process is a valid strategy for daemons + /// and IPC services, but the implementor has to keep in mind that + /// the memory for `function` and all of the code it invokes has to outlive + /// the threads lifetime. + /// This can be ensured by tethering the `target` thread to the owner of the + /// memory so it is automatically killed if the memory is returned to the OS. + in target: ?Process; + + /// The function that the thread will execute. in function: ThreadFunction; + + /// The argument passed to `function`. in arg: ?anyptr; + + /// The kernel will allocate at least this amount of bytes for the threads stack. + /// If zero is passed, the kernel will chose an implementation-defined amount of + /// stack for the thread. + /// + /// NOTE: There is no guarantee that the stack won't be larger than `stack_size` + /// bytes. in stack_size: usize; - out thread: Thread; + + /// The kind of thread that is created. + in kind: ThreadKind; + + /// The thread that was created. + /// NOTE: This thread handle will be bound to the calling process with the `at_least_weak` bind operation + /// to ensure access. + /// NOTE: The created thread will live logically inside the `target` process. + out thread: Thread; + + /// `target` is not a valid process resource. + error InvalidHandle; + + /// The `target` process is dead, but still has an alive handle. + error ZombieProcess; + error SystemResources; } - /// Kills the given thread with `exit_code`. + /// Kills the given thread. + /// + /// This is equivalent to the `target` thread executing `exit`, but triggered + /// from the outside. + /// + /// NOTE: This does not perform any stack unwinding and no code will be executed + /// in the `target` thread after a call to this function. + /// + /// NOTE: Passing in the current thread as `target` will make this function behave + /// like `exit` and it won't return. + /// + /// NOTE: Killing a thread stops the execution, but it does not destroy or release the + /// thread resource. + /// + /// NOTE: Killing an already exited thread is idempotent. syscall kill { in target: Thread; - in exit_code: ExitCode; error InvalidHandle; } + + /// Waits for the thread to exit. + /// + /// NOTE: The operation will complete immediately if `target` is already exited. + /// + /// NOTE: Awaiting the exit of the same thread multiple times is idempotent. + /// + /// NOTE: Multiple `WaitForExit` operations can be scheduled at once and + /// will all complete when the thread exits. + async_call WaitForExit { + in target: Thread; + + /// Informs how the thread exited: + /// - `true`: The thread exited by its thread function returning. + /// - `false`: The thread exited by invoking `exit` or `kill`. + out regular_exit: bool; + + error InvalidHandle; + } + + /// Suspends the execution of a thread. + /// + /// This means that a thread won't be scheduled for execution + /// until it is resumed. + /// + /// NOTE: If `target` is the current thread, this syscall + /// also yields implicitly and the syscall will + /// return when the thread is resumed. + syscall suspend { + /// The thread that shall be suspended. + /// If this value is `null`, the current thread will be suspended. + in target: ?Thread; + + /// Returned when `target` is not a valid thread resource. + error InvalidHandle; + + /// `target` is a thread that already exited. + error ThreadStopped; + } + + /// Resumes the execution of a thread. + /// + /// This means that the thread will be scheduled by the + /// operating system and continues execution. + /// + /// NOTE: Resuming an already active thread is idempotent and does nothing. + syscall resume { + /// The thread that should be resumed. + in target: Thread; + + /// Returned when `target` is not a valid thread resource. + error InvalidHandle; + + /// `target` is a thread that already exited. + error ThreadStopped; + } + + /// Changes the kind of a thread after creation. + /// + /// NOTE: If the last foreground thread of a regular process is changed + /// to `ThreadKind.background`, the process will be terminated and + /// this syscall may not return. + syscall set_kind { + /// The thread that shall be updated. + /// If this value is `null`, the current thread will be updated. + in target: ?Thread; + + /// The new kind this thread is. + in kind: ThreadKind; + + /// Returned when `target` is not a valid thread resource. + error InvalidHandle; + + /// `target` is a thread that already exited. + error ThreadStopped; + } + + /// Queries the thread kind. + syscall get_kind { + /// The thread that shall be queried. + /// If this value is `null`, the current thread will be queried. + in target: ?Thread; + + /// The kind of thread `target` is. + out kind: ThreadKind; + + /// Returned when `target` is not a valid thread resource. + error InvalidHandle; + + /// `target` is a thread that already exited. + error ThreadStopped; + } } namespace debug { + enum LogLevel : u8 { + /// The log message is about a critical, terminating error. The process or thread usually + /// cannot continue after such a log message. + item critical = 0; + + /// The log message is about a non-critical error. This means the process is not terminating + /// due to the error, but it might still be relevant to the user. + item err = 1; + + /// The log message is not an error, but informs about things that might still be relevant + /// to understand higher level failures like exceeded retries or failed connections. + item warn = 2; + + /// The log message informs the user about regular operations. + /// Nothing critical shall be logged with this level. + item notice = 3; + + /// The log message is only useful for debugging the process. + item debug = 4; + } + /// Writes to the system debug log. syscall write_log { - in log_level: LogLevel; - in message: str; + /// The level of severity this log message has. + in log_level: LogLevel; + + /// The message that shall be printed. + /// NOTE: `message` must be terminated with a `LF` to append a new line. + /// Log messages will be concatenated without a joining symbol, + /// so without a `LF` character, all log messages would appear on the same line. + in message: str; } /// Stops the process and allows debugging. + /// + /// When this syscall returns, the process will continue execution normally. + /// + /// NOTE: If the kernel has debugging disabled, this operation may be a no-op. + /// + /// LORE: This syscall is explicitly left under-defined as the debugging style + /// may change over time. At the time of writing (2026-02-06), this basically + /// just triggers a hardware breakpoint which will crash the kernel if no + /// hardware debugger is attached. + /// As this is designed as a low-level debug facility, it is fine for development + /// and semantics can later be improved. + /// The important part is that the syscall takes no arguments and returns neither + /// a value nor an error. syscall breakpoint { } } @@ -284,2959 +1161,5423 @@ namespace process { /// The size of the allocated memory block in bytes. /// /// NOTE: Passing 0 will never succeed. + /// + /// NOTE: The kernel ensures at least `size` bytes will be + /// usable in the returned `memory`. in size: usize; - /// The alignment - in ptr_align: u8; + /// The alignment of the pointer encoded as the number of + /// left-shifts on a one. + /// + /// This gives us a safer encoding as we only accept powers of two + /// anyways. + /// + /// RANGE: 0 .. 12 + in alignment_shift: u8; /// A non-`null` pointer that points to exactly @ref size bytes. /// /// NOTE: In practise, this might point to more than @ref size bytes, /// but the code must not assume *any* excess bytes may exist. - out memory: [*]u8; + out pointer: [*]u8; /// Is returned when the system is out of memory. - error SystemResource; + error SystemResources; } /// Returns previously allocated memory back to the process heap. + /// + /// NOTE: If `pointer` is not exactly a pointer previously returned by `allocate.pointer`, + /// the behaviour is undefined and could corrupt the system. + /// + /// NOTE: If `pointer` is released multiple times, the behaviour is undefined + /// and could corrupt the system. syscall release { - /// The complete chunk of memory previously allocated with @ref allocate. - in mem: []u8; - - /// The alignment that was passed to @ref allocate.ptr_align previously. - in ptr_align: u8; + /// The exact pointer previously returned in `allocate.pointer`. + in pointer: [*]u8; } } namespace monitor { - /// Queries all owned resources by a process. + /// Queries all existing process resources. + /// + /// NOTE: The order of processes is not necessarily stable between calls. syscall enumerate_processes { + /// An array that, if not `null`, will receive the list of processes available. + /// + /// NOTE: The process handles will be bound to the calling process with the `at_least_weak` bind operation + /// to ensure access. in processes: ?[]Process; + + /// The number of elements written inside `processes` or the total number of processes if `processes` is `null`. + /// + /// NOTE: If `processes` is not null, not more than `processes.len` is returned. out count: usize; + + error SystemResources; } - /// Queries all owned resources by a process. - syscall query_owned_resources { - in owner: Process; + /// Queries all bound resources by a process. + /// + /// NOTE: The order of resources is not necessarily stable between calls. + syscall query_bound_resources { + /// The process for which the resources should be queried. + in proc: Process; + + /// An array that, if not `null`, will receive the list of resources available. + /// + /// NOTE: The return resources will be bound to the calling process with the `at_least_weak` bind operation + /// to ensure access. in reslist: ?[]SystemResource; + + /// The number of elements written to `reslist` or the total number of resources if `reslist` is `null`. + /// + /// NOTE: If `reslist` is not null, not more than `reslist.len` is returned. out count: usize; + + /// `proc` is not a valid process resource. error InvalidHandle; + + error SystemResources; } /// Returns the total number of bytes the process takes up in RAM. syscall query_total_memory_usage { in proc: Process; - out count: usize; + + /// The total number of bytes the process currently allocates in RAM. + out size: usize; + + /// `proc` is not a valid process resource. error InvalidHandle; } /// Returns the number of dynamically allocated bytes for this process. syscall query_dynamic_memory_usage { in proc: Process; - out count: usize; + + /// The total number of heap bytes the process currently allocates in RAM. + out size: usize; + + /// `proc` is not a valid process resource. error InvalidHandle; } /// Returns the number of total memory objects this process has right now. syscall query_active_allocation_count { in proc: Process; + + /// The total number of heap allocations the process currently has active. out count: usize; + + /// `proc` is not a valid process resource. error InvalidHandle; } } } +/// This namespace contains functions and types related to a monotonic time base. +/// +/// NOTE: Functions inside this namespace are useful for measuring time or awaiting +/// timeouts. namespace clock { + //? TODO: Consider making `Absolute` based on a 960 kHz timer instead of a + //? 1 MHz/GHz timer precision. This would allow having a global unique + //? "audio clock compatible" time base in the system, allowing perfect + //? syntonization and correlation of different OS events. + + /// Time in nanoseconds since system startup. + enum Absolute : u64 { + /// The very first time point measurable. Nothing can happen before this point. + item system_start = 0; + + /// The very last point of system runtime that can be expressed in Ashet OS. + /// + /// LORE: This is a bit over 580 years of system runtime, which + /// is sufficient to fully cover the lifetime of the electronics, + /// users and probably even countries and societies. + /// Not quite infinity, but close enough for the computer it's running on. + item infinity = 0xFFFF_FFFF_FFFF_FFFF; + + ... + } + + /// A duration in nanoseconds. + enum Duration : u64 { + ... + } + /// Returns the time in nanoseconds since system startup. - /// This clock is monotonically increasing. + /// + /// NOTE: This clock never goes backwards (non-decreasing). + /// + /// NOTE: The returned value is expressed in nanoseconds, but the underlying hardware + /// may have a coarser resolution. In that case the value advances in steps. syscall monotonic { out time: Absolute; } - /// Sleeps until `clock.monotonic()` returns at least `timeout`. + /// Completes when `clock.monotonic() >= deadline`. + /// + /// NOTE: The timer completes immediately if `deadline` is already reached. async_call Timer { - /// Monotonic timestamp in nanoseconds until the operation completes. - in timeout: Absolute; + /// Monotonic timestamp in nanoseconds at which the operation completes. + in deadline: Absolute; } } +/// This namespace contains functions and types related to wall clock and calendar operations. +/// +/// NOTE: Leap seconds are implemented by stretching the last millisecond of a day +/// by an additional second, so the millisecond 23:59:59.999 is 1001 ms long. +/// The encoded `DateTime` value does not gain an extra representable millisecond; +/// the duration of the final millisecond is extended by the kernel when applicable. +/// +/// NOTE: Ashet OS uses [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time), not [International Atomic Time (TAI)](https://en.wikipedia.org/wiki/International_Atomic_Time). +/// +/// NOTE: The local time offset is always in minutes relative to the UTC time. +/// This means that it is computed by `local = utc + offset`. +/// As an example, consider `Europe/Berlin` (Winter), which is UTC+01:00: +/// - `offset = 60` +/// - `local = utc + 60` +/// - `utc = local - 60` +/// +/// NOTE: By default, the kernel uses UTC. Use `set_timezone_offset` or `load_timezone_data` to change that. +/// +/// +/// NOTE: The kernel maintains the local time zone in exactly one of two mutually exclusive modes: +/// - Manual fixed offset (configured via `set_timezone_offset`) +/// - Rule-based offset from tzdata (configured via `load_timezone_data`) +/// The most recently invoked of these two syscalls selects the active mode. +/// +/// LORE: The decision to use minute offsets instead of seconds is that +/// there is no real reason to support this level of precision, +/// as all current real time zones are only having quarter-hour +/// steps. Minutes allow a higher precision than that, but are not +/// unnecessarily high. +/// +/// LORE: Leap seconds are explicitly not part of the kernel API to keep the API +/// simple and predictable. +/// Having to consider leap seconds in each and every API will break more programs +/// than the OS pretending they don't exist on the API boundary. +/// There are two typical implementations for this behaviour: +/// - Leap smearing: The seconds of a day with a leap second are ever so slightly slower/faster, +/// so at the end of the day, the switch from 23:59:59 to 00:00:00 will be "steadily" (with +/// just a tiny fraction of pace difference). +/// - Duplicate second: The time between the wall clock displaying 23:59:59 and 00:00:00 is +/// two seconds instead of one. This means that the last second is taking twice as long as +/// a standard second. +/// For Ashet OS, which uses millisecond precision, the decision is to just make the last +/// millisecond of the day (23:59:59.999) be 1001 ms long. namespace datetime { - /// Get a calendar timestamp relative to UTC 1970-01-01. - /// Precision of timing depends on the hardware. - /// The return value is signed because it is possible to have a date that is - /// before the epoch. + //? + //? Basic date/time management and query + //? + + /// Encodes a packed structure that encodes a calendar date + wall clock time + /// into a single integer. + /// + /// NOTE: A DateTime value is always a UTC value. + /// + /// NOTE: The DateTime value is only valid when the `milliseconds_of_day` field + /// is in the defined range between 0 and 86,399,999 (both inclusive). + /// + /// For any value outside that range the DateTime value is considered *invalid*. + /// + /// NOTE: DateTime values form a *discrete linear order*; the encoding is an *injective*, + /// *strictly monotone order-embedding* into `i64`, whose image is a *gapped (non-contiguous) subset*; + /// decoding is a *partial function* on `i64`. + /// + /// This means not all `i64` values are valid DateTime values, + /// but we can trivially compare them as `i64` as the values + /// compare naturally (earlier points in time are smaller ints). + bitstruct DateTime : i64 { + /// The number of milliseconds inside the encoded day. + /// + /// RANGE: 0 to 86,399,999 + /// + /// LORE: Millisecond precision was chosen as it's the smallest + /// discrete time step in SI prefix units that fits into a u32 + /// value. + field milliseconds_of_day: u32; + + /// The number of days since the epoch, which is the `2000-01-01`. + /// + /// RANGE: 2000-01-01 = 0 + /// + /// LORE: The epoch was chosen to be the first January of 2000 as for + /// a non-UNIX timestamp system it doesn't necessarily make sense + /// to use the same epoch. + /// Using 2000 as the base year is kinda fun though, as it's a leap + /// year. + field days_since_epoch: i32; + } + + /// Returns the current date/time value. + /// + /// NOTE: The value returned by `now` may not be steady nor continuous. + /// As the wall clock can be adjusted by `set`, the value returned + /// by `now` can change abruptly, both in negative and positive + /// direction. + /// + /// NOTE: Precision of timing depends on the current hardware, + /// but should always be at least in seconds precision. + /// + /// NOTE: During a leap second adjustment, the final millisecond of a day + /// (23:59:59.999) may be extended, so repeated calls to `now` may + /// return the same `DateTime` value for longer than 1 ms. syscall now { - out datetime: DateTime; + /// The current date and time of the system. + out dt: DateTime; + } + + /// Updates the system's current date/time value. + /// + /// NOTE: Invoking `set` will make the value returned by `now` immediately jump + /// to the newly set value. + syscall set { + /// The new date and time of the system. + in dt: DateTime; + + /// Returned when `dt` does not encode a valid DateTime. + error InvalidValue; } - /// Sleeps until `datetime.now()` returns a point in time that comes after `when`. + /// Completes when `datetime.now() >= when`. + /// + /// NOTE: A call to `set` may trigger all active alarm calls that + /// are now satisfied. async_call Alarm { /// Earliest possible date time of when the alarm triggers. in when: DateTime; + + /// Returned when `when` does not encode a valid DateTime. + error InvalidValue; } -} + //? + //? Timezone Management + //? -namespace video { - /// Returns a list of all video outputs. - /// - /// If `ids` is `null`, the total number of available outputs is returned; - /// otherwise, up to `ids.len` elements are written into the provided array - /// and the number of written elements is returned. - syscall enumerate { - in ids: ?[]VideoOutputID; - out count: usize; + /// Gets the current offset between local time and UTC. + syscall get_timezone_offset + { + /// The offset of the local time to UTC in minutes. + /// + /// RANGE: -1440 - 1440 + /// + /// NOTE: In tzdata mode, this returns the offset that applies at `datetime.now()` + /// and may change over time as time zone rules change. + out minutes: i16; } - /// Acquire exclusive access to a video output. - syscall acquire { - in output_id: VideoOutputID; - out output: VideoOutput; - error NotAvailable; - error NotFound; - error SystemResources; - } + /// Sets the current offset between local time and UTC. + syscall set_timezone_offset + { + /// The offset of the local time to UTC in minutes. + /// + /// RANGE: -1440 - 1440 + in minutes: i16; - /// Returns the current resolution - syscall get_resolution { - in output: VideoOutput; - out resolution: Size; - error InvalidHandle; + /// The provided 'minutes' offset was not in the legal range. + error InvalidZoneOffset; } - /// Returns a pointer to linear video memory, row-major. - /// Pixels rows will have a stride of the current video buffer width. - /// The first pixel in the memory is the top-left pixel. - syscall get_video_memory { - in output: VideoOutput; - out memory: VideoMemory; - error InvalidHandle; - } - - /// Completes when the video output has fully scanned out an image and is now performing the v-blanking. + /// Loads a "timezone data" file according to the [tz database](https://en.wikipedia.org/wiki/Tz_database). /// - /// This allows frame-synchronized presentation of video data. - async_call WaitForVBlank { - in output: VideoOutput; - error InvalidHandle; + /// This allows setting the automatic update of the current local time zone offset according to + /// the rules encoded in the timezone data. + /// + /// LORE: The decision to use tzdata was pretty simple: It's a standardized format + /// that has proven over time and solves pretty much all of our issues already + /// in a good way. + syscall load_timezone_data + { + /// The binary TZif blob of the timezone data file. + /// + /// NOTE: [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536) specifies the + /// format accepted by this syscall. + /// + /// NOTE: TODO: Specify exact supported version and features of TZif. + in data: bytestr; + + /// The system is out of resources and cannot load the timezone data. + error SystemResources; + + /// The data is not a valid timezone data file. + error InvalidData; } -} -namespace random { - /// Fills the provided pointer with random bytes from the entropy pool. + /// Queries the timezone offset for a given point in time. /// - /// If the entropy pool runs out of entropy bits, this call does not block - /// and draw bytes from the pool. - syscall get_soft_random { - in data: bytebuf; + /// NOTE: This function returns either: + /// - the fixed manual offset set via `set_timezone_offset` (Manual mode), or + /// - the `dt`-dependent offset determined from tzdata (Tzdata mode). + syscall get_timezone_offset_at + { + /// The point in time to query the zone offset for. + in dt: DateTime; + + /// The local time offset in minutes. + out minutes: i16; + + /// Returned when `dt` does not encode a valid DateTime. + error InvalidValue; } - /// Fills the provided buffer with given length amount of random bytes. + //? + //? Gregorian Calendar APIs + //? + + /// A structure encoding a date in the [Proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar), + /// which means it can encode dates prior to 1582. /// - /// This call blocks until the entropy pool has enough entropy to fill - /// an entire hash to draw from. - async_call GetStrictRandom { - in data: bytebuf; - } -} + /// LORE: This structure isn't a necessity for the kernel API, but is introduced + /// as a way of saving code size and memory, as most western applications + /// will use the Gregorian calendar, so it makes sense to share the implementation + /// for a conversion from/to `DateTime` in the kernel. + struct GregorianDate { + /// The astronomical year of the date. + /// NOTE: This means that `year = 0` means 1 BCE. + /// RANGE: -32768 - 32767 + field year: i16; -namespace input { - /// Waits for an input event and completes when any input was done. - async_call GetEvent { - out event: InputEvent; - error InProgress; - error NonExclusiveAccess; - } -} + /// RANGE: 1-12 + field month: u8; -namespace network { - enum IP_Type : u8 { - item ipv4; - item ipv6; - } + /// RANGE: 1-31 + field day: u8; - struct IPv4 { - field addr: [4]u8 ; //? TODO: align(4) - } + /// RANGE: 0-23 + field hour: u8; - struct IPv6 { - field addr: [16]u8; //? TODO: align(4) - field zone: u8; - } + /// RANGE: 0-59 + field minute: u8; - struct IP { - field type: IP_Type; - field addr: AnyAddr; + /// RANGE: 0-59 + /// NOTE: 60 is *not allowed*. See the note on `datetime` for more information. + field second: u8; - union AnyAddr { - field v4: IPv4; - field v6: IPv6; - } + /// RANGE: 0-999 + field millis: u16; } - struct EndPoint { - field ip: IP; - field port: u16; - } + /// Converts a gregorian date into a DateTime. + syscall from_gregorian { + /// The gregorian date that shall be converted into a date time. + in gregorian: GregorianDate; - namespace dns { - //? TODO + /// The offset to UTC in minutes for the date. + /// NOTE: Use 0 for UTC. + /// RANGE: -1440 - 1440 + in local_offset: i16; + + /// The resulting datetime value for `gregorian`. + out dt: DateTime; + + /// The gregorian date contains an invalidly specified date. + error InvalidValue; + + /// The local time zone offset is not in the legal range. + error InvalidZoneOffset; } - namespace udp { - /// Creates a new UDP socket. - syscall create_socket { - out socket: UdpSocket; - error SystemResources; - } + /// Converts a DateTime value into a gregorian date. + syscall to_gregorian { + /// The DateTime value that shall be converted into a gregorian date. + in dt: DateTime; - async_call Bind { - in socket: UdpSocket; - in bind_point: EndPoint; - out endpoint: EndPoint; - error IllegalArgument; - error AddressInUse; - error IllegalValue; - error InvalidHandle; - error SystemResources; - } + /// The offset to UTC in minutes for the date. + /// NOTE: Use 0 for UTC. + /// RANGE: -1440 - 1440 + in local_offset: i16; - async_call Connect { - in socket: UdpSocket; - in target: EndPoint; - error AlreadyConnected; - error AlreadyConnecting; - error AddressInUse; - error BufferError; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// The resulting gregorian date. + out gregorian: GregorianDate; - async_call Disconnect { - in socket: UdpSocket; - error InvalidHandle; - error NotConnected; - error SystemResources; - } + /// The date/time value contains an invalid value. + error InvalidValue; - async_call Send { - in socket: UdpSocket; - in data: bytestr; - out bytes_sent: usize; - error BufferError; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error NotConnected; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// The local time zone offset is not in the legal range. + error InvalidZoneOffset; - async_call SendTo { - in socket: UdpSocket; - in receiver: EndPoint; - in data: bytestr; - out bytes_sent: usize; - error BufferError; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// `dt` points to a year not representable by `GregorianDate`. + error OutOfRange; + } - async_call ReceiveFrom { - in socket: UdpSocket; - in buffer: bytebuf; - out bytes_received: usize; - out sender: EndPoint; - error BufferError; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// Converts a date/time value into the current local gregorian date. + /// + /// NOTE: This function utilizes the current time zone information and + /// may have a dynamic offset to UTC. + syscall to_gregorian_local { + /// The DateTime value that shall be converted into a gregorian date. + in dt: DateTime; + + /// The local date. + out gregorian: GregorianDate; + + /// The date/time value contains an invalid value. + error InvalidValue; + + /// `dt` points to a year not representable by `GregorianDate`. + error OutOfRange; } - namespace tcp { - /// Creates a new TCP socket. - syscall create_socket { - out socket: TcpSocket; - error SystemResources; - } + /// Converts a local gregorian date/time into a generic date/time value. + /// + /// NOTE: This function utilizes the current time zone information and + /// may have a dynamic offset to UTC. + /// + /// NOTE: If the local time is non-existent, `occurrence` is ignored. + /// If the local time is ambiguous, `adjustment` is ignored. + syscall from_gregorian_local { + /// The gregorian date in the local time zone. + in gregorian: GregorianDate; + + /// How to handle a well-formed local wall clock time that cannot be mapped + /// to a UTC timestamp because it does not exist in the current time zone rules. + /// + /// NOTE: This may happen due to daylight saving time or similar rules. + in adjustment: MissingTimeAdjustment; - async_call Bind { - in socket: TcpSocket; - in bind_point: EndPoint; - out bound_endpoint: EndPoint; - error AddressInUse; - error IllegalValue; - error InvalidHandle; - error SystemResources; - } + /// How to resolve ambiguities when a wall clock time appears multiple times. + /// + /// NOTE: This may happen due to daylight saving time or similar rules. + in occurrence: DuplicateTimeOccurrence; - async_call Connect { - in socket: TcpSocket; - in target: EndPoint; - error AlreadyConnected; - error AlreadyConnecting; - error BufferError; - error ConnectionAborted; - error ConnectionClosed; - error ConnectionReset; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// The date/time value representing the given date. + out dt: DateTime; - async_call Send { - in socket: TcpSocket; - in data: bytestr; - out bytes_sent: usize; - error BufferError; - error ConnectionAborted; - error ConnectionClosed; - error ConnectionReset; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error NotConnected; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// The gregorian date contains an invalidly specified date. + error InvalidValue; - async_call Receive { - in socket: TcpSocket; - in buffer: bytebuf; - /// if true, will read until `buffer.len` bytes arrived. otherwise will read until the end of a single packet - in read_all: bool; - out bytes_received: usize; - error AlreadyConnected; - error AlreadyConnecting; - error BufferError; - error ConnectionAborted; - error ConnectionClosed; - error ConnectionReset; - error IllegalArgument; - error IllegalValue; - error InProgress; - error InvalidHandle; - error LowlevelInterfaceError; - error NotConnected; - error OutOfMemory; - error Routing; - error SystemResources; - error Timeout; - } + /// The gregorian time maps a non-existing wall clock time and + /// `adjustment` was `reject`. + error NonexistentLocalTime; + /// The gregorian time maps an ambiguous wall clock time and + /// `occurrence` was `reject`. + error AmbiguousLocalTime; } -} + /// Enumeration of the variants how missing wall clock times will be resolved. + enum MissingTimeAdjustment : u8 { + /// The time is not adjusted, but rejected and yields an error. + item reject = 0; -/// A file or directory on Ashet OS can be named with any legal UTF-8 sequence -/// that does not contain `/` and `:`. It is recommended to only create file names -/// that are actually typeable on the operating system tho. -/// -/// There are some special file names: -/// - `.` is the "current directory" selector and does not add to the path. -/// - `..` is the "parent directory" selector and navigates up in the directory hierarchy if possible. -/// - Any sequence of upper case ASCII letters and digits (`A-Z`, `0-9`) that ends with `:` is a file system name. This name specifies -/// the root directory of a certain file system. -/// -/// Paths are either a relative or absolute addyessing of a file system entity. -/// Paths are composed of a sequence of names, each name separated by `/`. -/// A file system name is only legal as the first element of a path sequence, making the path an absolute path. -/// -/// There is a limit on how long a file/directory name can be, but there's no limit on how long a total -/// path can be. -/// -/// Here are some examples for valid paths: -/// - `example.txt` -/// - `docs/wiki.txt` -/// - `SYS:/apps/editor/code` -/// - `USB0:/foo/../bar` (which is equivalent to `USB0:/bar`) -/// -/// The filesystem that is used to boot the OS from has an alias `SYS:` that is always a legal way to address this file system. -namespace fs { - /// Finds a file system by name - syscall find_filesystem { - in name: str; - out id: FileSystemId; - } + /// The time is adjusted to the first possible past point in time. + /// EXAMPLE: `02:30:00.000` is mapped to `01:59:59.999`. + item past = 1; - /// Flushes all open files to disk. - async_call Sync { - error DiskError; - } + /// The time is adjusted to the first possible future point in time. + /// EXAMPLE: `02:30:00.000` is mapped to `03:00:00.000`. + item future = 2; - /// Gets information about a file system. - /// Also returns a `next` id that can be used to iterate over all filesystems. - /// The `system` filesystem is guaranteed to be the first one. - async_call GetFilesystemInfo { - in fs_id: FileSystemId; - out info: FileSystemInfo; - out next: FileSystemId; - error DiskError; - error InvalidFileSystem; + /// The time is adjusted to the closest possible point in time. + /// EXAMPLE: `02:29:00.000` is mapped to `01:59:59.999`. + /// EXAMPLE: `02:30:00.000` is mapped to `03:00:00.000`. + /// EXAMPLE: `02:31:00.000` is mapped to `03:00:00.000`. + item closer = 3; } - /// opens a directory on a filesystem - async_call OpenDrive { - in fs_id: FileSystemId; - in path: str; - out dir: Directory; - error DiskError; - error FileNotFound; - error InvalidFileSystem; - error InvalidPath; - error NotADir; - error SystemFdQuotaExceeded; - error SystemResources; - } + /// Enumeration of the variants how a wall clock time that can occur multiple times + /// is handled. + enum DuplicateTimeOccurrence : u8 { + /// The time is not adjusted, but rejected and yields an error. + item reject = 0; - /// opens a directory relative to the given dir handle. - async_call OpenDir { - in start_dir: Directory; - in path: str; - out dir: Directory; - error DiskError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NotADir; - error SystemFdQuotaExceeded; - error SystemResources; - } + /// If the time is ambiguous, assume the earlier variant. + /// EXAMPLE: `02:30` is 2.5 hours past midnight. + item earlier = 1; - /// closes the directory handle - async_call CloseDir { - in dir: Directory; - error InvalidHandle; + /// If the time is ambiguous, assume the later variant. + /// EXAMPLE: `02:30` is 3.5 hours past midnight. + item later = 2; } +} - /// resets the directory iterator to the starting point - async_call ResetDirEnumeration { - in dir: Directory; - error DiskError; - error InvalidHandle; - error SystemResources; +/// This namespace contains items related to presenting video data. +namespace video { + /// Index of the systems video outputs. + enum VideoOutputID : u8 { + /// The primary video output + item primary = 0; + ... } - /// returns the info for the current file or "eof", and advances the iterator to the next entry if possible - async_call EnumerateDir { - in dir: Directory; - out eof: bool; - out info: FileInfo; - error DiskError; - error InvalidHandle; - error SystemResources; + /// Returns a list of all video outputs. + /// + /// If `ids` is `null`, the total number of available outputs is returned; + /// otherwise, up to `ids.len` elements are written into the provided array + /// and the number of written elements is returned. + syscall enumerate { + in ids: ?[]VideoOutputID; + out count: usize; } - /// deletes a file or directory by the given path. - async_call Delete { - in dir: Directory; - in path: str; - in recurse: bool; - error DiskError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - } + /// The video output resource is an exclusive access token to a + /// video output. + /// + /// It allows updating the displayed pixel data and waiting for the + /// vertical blanking interval of the display data. + resource VideoOutput { } - /// creates a new directory relative to dir. If `path` contains subdirectories, all - /// directories are created. - async_call MkDir { - in dir: Directory; - in path: str; - in mkopen: bool; - out new_dir: Directory; - error DiskError; - error Exists; - error InvalidHandle; - error InvalidPath; - } + /// Acquire exclusive access to a video output. + syscall acquire { + in output_id: VideoOutputID; - /// returns the type of the file/dir at path, also adds size and modification dates - async_call StatEntry { - in dir: Directory; - in path: str; - out info: FileInfo; - error DiskError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - } + /// The resource created from `output_id`. + out output: VideoOutput; - /// renames a file inside the same file system. - /// NOTE: This is a cheap operation and does not require the copying of data. - async_call NearMove { - in src_dir: Directory; - in src_path: str; - in dst_path: str; - error DiskError; - error Exists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - } + /// Exclusive access is already held for the video output identified by `output_id`. + error AlreadyExists; - /// moves a file or directory between two unrelated directories. Can also move between different file systems. - /// NOTE: This syscall might copy the data. - async_call FarMove { - in src_dir: Directory; - in src_path: str; - in dst_dir: Directory; - in dst_path: str; - error DiskError; - error Exists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NoSpaceLeft; - } + /// `output_id` is not a valid video output id. + error InvalidId; - /// copies a file or directory between two unrelated directories. Can also move between different file systems. - async_call Copy { - in src_dir: Directory; - in src_path: str; - in dst_dir: Directory; - in dst_path: str; - error DiskError; - error Exists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NoSpaceLeft; + error SystemResources; } - /// opens a file from the given directory. - async_call OpenFile { - in dir: Directory; - in path: str; - in access: FileAccess; - in mode: FileMode; - out handle: File; - error DiskError; - error Exists; - error FileAlreadyExists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NoSpaceLeft; - error SystemFdQuotaExceeded; - error SystemResources; - error WriteProtected; - } + /// Returns the resolution of `output` in pixels. + syscall get_resolution { + in output: VideoOutput; - /// closes the handle and flushes the file. - async_call CloseFile { - in file: File; - error DiskError; - error InvalidHandle; - error SystemResources; - } + out resolution: Size; - /// makes sure this file is safely stored to mass storage device - async_call FlushFile { - in file: File; - error DiskError; - error InvalidHandle; - error SystemResources; + /// `output` is not a valid video output resource. + error InvalidHandle; } - /// directly reads data from a given offset into the file. no streaming API to the kernel - async_call Read { - in file: File; - in offset: u64; - in buffer: bytebuf; - out count: usize; - error DiskError; - error InvalidHandle; - error SystemResources; - } + /// Completes when the video output has fully scanned out an image and is now performing the v-blanking. + /// + /// This allows frame-synchronized presentation of video data. + /// + /// NOTE: All scheduled `WaitForVBlank` operations complete at the start of the next vertical blanking period. + /// + /// This means that a schedule during the current vertical blanking period does not immediately complete + /// the operation, but delays by nearly a full frame. + async_call WaitForVBlank { + in output: VideoOutput; - /// directly writes data to a given offset into the file. no streaming API to the kernel - async_call Write { - in file: File; - in offset: u64; - in buffer: bytestr; - out count: usize; - error DiskError; + /// `output` is not a valid video output resource. error InvalidHandle; - error NoSpaceLeft; - error SystemResources; - error WriteProtected; } - /// allows us to get the current size of the file, modification dates, and so on - async_call StatFile { - in file: File; - out info: FileInfo; - error DiskError; - error InvalidHandle; - error SystemResources; - } + /// Specifies how `WritePixels` will upload the pixels. + enum PresentMode : u8 { + /// The pixel data is written immediately. + /// + /// NOTE: This mode will immediately upload the pixel data and + /// will not await a vertical blanking period. This means the + /// upload is likely to create visual glitches or tearing. + item immediate = 0; - /// Resizes the file to the given length in bytes. Can be also used to truncate a file to zero length. - async_call Resize { - in file: File; - in length: u64; - error DiskError; - error InvalidHandle; - error NoSpaceLeft; - error SystemResources; + /// The kernel attempts a tearing free upload of the pixel data. + /// + /// This means the kernel attempts to align the upload with the + /// vertical blanking period. + /// + /// NOTE: This mode is best-effort, and does not guarantee the + /// video data is uploaded tearing-free. + item vblank = 1; } -} + /// Uploads pixels to a video output. + /// + /// NOTE: If `destination` would update a zero-sized area (`width` or `height` is zero), + /// the operation is a no-op and completes immediately. + /// + /// LORE: Originally, we had the ability to directly get a pointer + /// to the video outputs buffer. + /// As convenient as it is, it implicitly imposed the requirement + /// for the kernel to potentially allocate a pixel buffer if the + /// video output cannot actually provide the video memory inside + /// the systems main memory. + /// + /// This forced the kernel to periodically upload an allocated buffer + /// to external video devices, which is both inefficient and error prone. + /// + /// This syscall + `Buffer` sidestep this problem by making the access of a + /// memory-mapped video memory fallible without removing the ability for a generic + /// upload procedure. + async_call WritePixels { + /// The output which should receive the pixel data. + in output: VideoOutput; -namespace shm { - /// Constructs a new shared memory object with `size` bytes of memory. - /// Shared memory can be written by all processes without any memory protection. - syscall create { - in size: usize; - out handle: SharedMemory; - error SystemResources; - } + /// The portion of the video buffer that should be updated. + in destination: Rectangle; - /// Returns the number of bytes inside the given shared memory object. - syscall get_length { - in handle: SharedMemory; - out length: usize; - error InvalidHandle; - } + /// Pointer to the top-left pixel of `destination`. + /// + /// NOTE: The order inside this array is row-major. + /// This means that `pixels[1]` is the pixel at `(destination.x + 1, destination.y)` + /// and `pixels[stride]` is the pixel at `(destination.x, destination.y + 1)`. + /// + /// NOTE: Each scanline starts at `y * stride` elements apart and the buffer must contain + /// at least `destination.height` scanlines. + in pixels: []const Color; - /// Returns a pointer to the shared memory. - syscall get_pointer { - in handle: SharedMemory; - out pointer: [*]align(16) u8; + /// The length of a scanline in `pixels` in elements. + in stride: usize; + + /// Determines when to perform the pixel data write. + in mode: PresentMode; + + /// `output` is not a valid video output resource. error InvalidHandle; - } -} + /// Returned when `pixels` does not hold enough pixels to update `destination`. + /// + /// This means that `pixels.len` is less than `stride * max(0, destination.height - 1) + destination.width`. + /// + /// NOTE: This error is only returned if `destination.height > 0`. + error BufferSize; -namespace pipe { - /// Spawns a new pipe with `fifo_length` elements of `object_size` bytes. - /// If `fifo_length` is 0, the pipe is synchronous and can only send data - /// if a `read` call is active. Otherwise, up to `fifo_length` elements can be - /// stored in a FIFO. - syscall create { - in object_size: usize; - in fifo_length: usize; - error SystemResources; - out handle: Pipe; - } + /// `stride` is less than `destination.width`. + error InvalidStride; - /// Returns the length of the pipe-internal FIFO in elements. - syscall get_fifo_length { - in handle: Pipe; - out length: usize; - error InvalidHandle; + /// `destination` is outside the actual video buffer resolution. + error InvalidRegion; } - /// Returns the size of the objects stored in the pipe. - syscall get_object_size { - in handle: Pipe; - out size: usize; - error InvalidHandle; - } + /// A buffer mapping provides a memory-mapped view into a + /// front- or backbuffer of a video output. + /// + /// This allows uploading pixel data without the need for a `WritePixels` operation. + /// + /// NOTE: Not every `VideoOutput` supports a buffer mapping. + resource BufferMapping { } - /// Writes elements from `data` into the given pipe. - async_call Write { - in handle: Pipe; - /// Pointer to the first element. Length defines how many elements are to be transferred. - in data: bytestr; - /// Distance between each element in `data`. Can be different from the pipes element size - /// to allow sparse data to be transferred. - /// If `0`, it will use the `object_size` property of the pipe. - in stride: usize; - /// Defines how the write should operate. - in mode: PipeMode; - /// Numbert of elements written into the pipe. - out count: usize; - } + enum BufferKind : u8 { + /// A front buffer uses the same data as the scanout mechanism. + /// This means that any write to this buffer is *directly* visible + /// as soon as the video output scans out the written pixel locations. + /// + /// NOTE: This means that writes may produce tearing or other visual + /// glitches. + /// + /// NOTE: `Present` is not required to make the changes visible. + item front_buffer = 0; - /// Reads elements from a pipe into `buffer`. - async_call Read { - in handle: Pipe; - /// Points to the first element to be received. - in buffer: bytebuf; - /// Distance between each element in `buffer`. Can be different from the pipes element size - /// to allow sparse data to be transferred. - /// If `0`, it will use the `object_size` property of the pipe. - in stride: usize; - /// Defines how the read should operate. - in mode: PipeMode; - /// Number of elements read. - out count: usize; + /// A back buffer is a second buffer that is not used for scanning out + /// pixel data. + /// + /// This means that writes to a back buffer will never appear on the + /// video output unless the buffer is swapped/copied to the front buffer. + /// + /// To perform this copy/swap, the `Present` operation shall be used. + /// + /// NOTE: It is possible, but not recommended to perform a manual copy + /// from a back buffer mapping to a front buffer mapping. + item back_buffer = 1; } -} + /// Creates a memory mapping for the front or the back buffer of a video output. + /// + /// NOTE: Not every video output supports memory mappings at all. Some video outputs + /// only support a single mode of memory mapping. + /// + /// The supported combinations are: + /// - No mapping support. + /// - Only front buffer. + /// - Only back buffer. + /// - Both front and back buffer. + /// + /// When a buffer type is not supported, `Unsupported` is returned. + /// + /// NOTE: There can be only a single mapping for the front and the back buffer. + /// This means for each video output, a maximum of two `BufferMapping` resources + /// can exist. + /// + /// NOTE: A buffer mapping is implicitly destroyed when its associated video output is + /// destroyed. This is necessary as the destruction of the video output resource + /// revokes access to the video device, and thus also revokes access through memory + /// mappings. + syscall create_buffer_mapping { + in output: VideoOutput; -namespace sync { - /// Creates a new `SyncEvent` object that can be used to synchronize - /// different processes. - syscall create_event { - out event: SyncEvent; - error SystemResources; - } + /// Which buffer should be mapped. + in requested_kind: BufferKind; - /// Completes one `WaitForEvent` IOP waiting for the given event. - syscall notify_one { - in event: SyncEvent; - error InvalidHandle; - } + out buffer: BufferMapping; - /// Completes all `WaitForEvent` IOP waiting for the given event. - syscall notify_all { - in event: SyncEvent; + /// `output` is not a valid video output resource. error InvalidHandle; + + /// The requested buffer type is not supported by the `output` device. + error Unsupported; + + /// A buffer mapping for the `requested_kind` of the video output + /// already exists. + error AlreadyExists; + + error SystemResources; } - /// Waits for the given `SyncEvent` to be notified. - async_call WaitForEvent { - in event: SyncEvent; + /// Applies the changes inside `buffer` and guarantees they + /// are visible afterwards. + /// + /// NOTE: For a front buffer, no data movement will happen, but + /// `mode` may still make `Present` await the next vertical blanking + /// period. + /// + /// NOTE: It is not specified if a `Present` for a back buffer is performing a + /// buffer swap operation or a buffer copy operation. + /// + /// NOTE: If `mode == PresentMode.immediate` and `buffer` is a front buffer, the + /// operation completes immediately. + async_call Present + { + /// The buffer mapping that shall be presented. + in buffer: BufferMapping; + + /// Determines when to perform the pixel data update. + in mode: PresentMode; + + /// `buffer` is not a valid buffer mapping resource. error InvalidHandle; } - /// Creates a new mutual exclusion. - syscall create_mutex { - error SystemResources; - out mutex: Mutex; - } + /// A descriptor of memory-accessible pixel buffer. + /// + /// It is laid out row-major and `base[0]` is the top-left pixel + /// of the mapped image. + struct VideoMemory { + /// Pointer to the first pixel of the first scanline. + /// + /// Each scanline is `.stride` elements separated from + /// each other and contains `width` valid elements. + /// + /// There are `height` total scanlines available. + field base: [*]align(4) Color; - /// Tries to lock a mutex and returns if it was successful. - syscall try_lock { - in mutex: Mutex; - out is_locked: bool; - error InvalidHandle; - } + /// Length of a scanline in elements. + field stride: usize; - /// Unlocks a mutual exclusion. Completes a single `Lock` IOP if it exists. - syscall unlock { - in mutex: Mutex; - error InvalidHandle; + /// Number of valid elements in a scanline + field width: u16; + + /// Number of valid scanlines. + field height: u16; } - /// Locks a mutex. Will complete once the mutex is locked. - async_call Lock { - in mutex: Mutex; - error InvalidHandle; + /// Returns a pointer to linear video memory, row-major. + /// + /// NOTE: The pointer inside `memory` is only valid until the next `Present` operation + /// for any front or back buffer mapping for the associated video output or until + /// the buffer mapping is destroyed. + /// + /// This requires careful management and it is not recommended to share different + /// `BufferMapping` resources with other actors. + syscall get_video_memory { + in buffer: BufferMapping; + + /// The descriptor of the memory mapped video buffer. + out memory: VideoMemory; + + /// `buffer` is not a valid buffer mapping resource. + error InvalidHandle; } } -namespace draw { - /// Returns the font data for the given font name, if any. - syscall get_system_font { - in font_name: str; - out handle: Font; - error FileNotFound; - error SystemResources; - } +//? TODO: Review this namespace. +namespace audio { +//? TODO: Write this namespace. +//? +//? Your primary interface for audio streams is the ability to enqueue PCM/MIDI/ChipWrites at certain sample positions relative to your stream start. +//? +//? Later PCM schedules stop previous PCMs at exactly that sample, so only a single PCM data stream is active per logical audio stream. +//? MIDI should be obvious. +//? ChipWrites implements native support for audio chips like a MOS 6581 or AY-3-8910 where you can sample-precisely schedule register +//? writes to your audio chips to create multi-channel-multi-tier audio creations. +} - /// Creates a new custom font from the given data. - syscall create_font { - in data: bytestr; - out handle: Font; - error InvalidData; - error SystemResources; +/// This namespace contains items related to entropy management. +namespace random { + /// Fills `data` with random bytes. + /// + /// This call never waits. Bytes are generated from the kernel DRBG. + /// If the DRBG cannot be (re)seeded due to insufficient newly collected entropy, + /// output is still produced (possibly without reseeding). + /// + /// NOTE: Do not use this for key generation unless the system guarantees + /// the DRBG has been seeded at least once (see `GetStrictRandom`). + syscall get_soft_random { + /// The buffer that should be filled with random bytes. + in data: bytebuf; } - /// Returns true if the given font is a system-owned font. - syscall is_system_font { - in font: Font; - out system_font: bool; - error InvalidHandle; + /// Fills `data` with random bytes, but only after the kernel DRBG is seeded. + /// + /// This async call completes once the entropy pool reached the minimum seeding + /// threshold, then generates bytes from the kernel DRBG. + /// + /// NOTE: May take a substantial amount of time on systems with weak entropy sources. + async_call GetStrictRandom { + /// The buffer that should be filled with random bytes. + in data: bytebuf; } - /// Measures the size of a text string. - /// - /// NOTE: This function accepts strings using the LF line separator - /// and will return the height of all lines and the width of - /// the longest line. - syscall measure_text_size { - in font: Font; - in text: str; - out size: Size; - error InvalidHandle; - } + //? TODO: add "add entropy" syscall +} - /// Creates a new in-memory framebuffer that can be used for offscreen painting. - syscall create_memory_framebuffer { - in size: Size; - out handle: Framebuffer; - error SystemResources; - } +/// +/// Input devices, input groups, and input event delivery. +/// +/// The kernel exposes input in two ways: +/// - **Input groups**: loss-tolerant, bounded, strictly ordered queues. +/// - **Device waits**: edge-triggered fanout waits that do not buffer and may be lossy. +/// +/// LORE: Input groups exist so userland can define *which* devices it wants to consume, +/// while still retaining a strong ordering across multiple devices inside that group. +/// The previous "global merged queue" model made it hard to correctly split input +/// between unrelated consumers. +/// +/// +/// Event fusing rules: +/// +/// LORE: To reduce event pressure and avoid jitter, the kernel may fuse continuous events inside +/// group queues without changing the final reconstructed state. +/// +/// Rules: +/// - Only continuous events may be fused (e.g. relative motion, absolute motion, wheel, analog axis). +/// - Discrete events are never fused (e.g. key press/release, button press/release, text input). +/// - Fusing never combines different devices and never combines different event types. +/// - Fusing occurs only inside group queues and does not add a fixed input latency. +/// - A fused event uses the timestamp of the last fused constituent. +/// +//? TODO: Expose/standardize the exact fusing window (e.g. ~40ms) if userland ever needs it. +namespace input { + //? TODO: Add system call to upload a new potential keyboard layout. - /// Creates a new framebuffer based off a video output. Can be used to output pixels - /// to the screen. - syscall create_video_framebuffer { - in output: VideoOutput; - out handle: Framebuffer; - error InvalidHandle; - error SystemResources; + /// + /// Opaque identifier for an input device. + /// + /// Device ids are allocated by the kernel when devices are added (system start or hotplug) + /// and are dropped when the device is removed/unplugged. + /// + /// The ids are not assigned in a stable manner, this means the same device will receive a + /// different device id if removed and readded later. Also kernel enumeration at system + /// start has no specified order and devices will not have a stable id. + /// + /// NOTE: A `DeviceId` obtained from `enumerate_devices` is guaranteed to be valid only + /// until the calling thread yields to the scheduler. + /// After a yield, any syscall using that `DeviceId` may fail with `InvalidDevice` when + /// the device was removed. + /// + /// NOTE: Devices are not system resources. They do not have ownership semantics. + /// + /// NOTE: Device ids are allocated by the kernel in a monotonic manner, so it takes around + /// 4 billion plug/unplug operations until a device id is reused again. + /// This will take a while. + /// + /// LORE: It doesn't make sense to handle devices as system resources as they can be + /// potentially removed at runtime by external means and holding such a resource + /// afterwards would make the resource invalid anyways. Destroying a device resource + /// would also have no semantic meaning, as the physical device would still be plugged + /// into the system. + /// + enum DeviceId : u32 { + /// Special value used when an event has no originating device. + /// + /// NOTE: This value is only used as `InputEvent.device` for group-injected events. + /// It is not a valid target for `GetDeviceEvent` or `emit_device_event`. + item synthetic = 0; + + ... } - /// Creates a new framebuffer that allows painting into a GUI window. - syscall create_window_framebuffer { - in window: Window; - out handle: Framebuffer; - error InvalidHandle; - error SystemResources; + /// Enumerates all currently available input devices. + /// + /// If `ids` is `null`, the total number of available devices is returned; + /// otherwise, up to `ids.len` elements are written into the provided array + /// and the number of written elements is returned. + /// + /// NOTE: Returned ids are only guaranteed to be valid until the calling thread yields. + syscall enumerate_devices { + in ids: ?[]DeviceId; + out count: usize; } - /// Creates a new framebuffer that allows painting into a widget. - syscall create_widget_framebuffer { - in widget: Widget; - out handle: Framebuffer; - error InvalidHandle; - error SystemResources; + + /// Describes the broad class of an input device. + enum DeviceClass : u8 { + item unknown = 0; + item keyboard = 1; + item mouse = 2; + item gamepad = 3; + item joystick = 4; + item @"3d_mouse" = 5; + ... } - /// Returns the type of a framebuffer object. - syscall get_framebuffer_type { - in fb: Framebuffer; - out type: FramebufferType; - error InvalidHandle; + /// Describes the transport/protocol of an input device. + enum DeviceProtocol : u8 { + item unknown = 0; + item usb = 1; + item bluetooth = 2; + item serial = 3; + item bitbang = 4; + item network = 5; + ... } - /// Returns the size of a framebuffer object. - syscall get_framebuffer_size { - in fb: Framebuffer; - out size: Size; - error InvalidHandle; + /// Capability flags of an input device. + bitstruct DeviceCapabilities : u16 { + /// Device can emit HID-style key usage codes. + field keys: bool; + + /// Device provides relative pointer motion events. + field rel_pointer: bool; + + /// Device provides absolute pointer position events. + field abs_pointer: bool; + + reserve u13 = 0; } - /// Returns the video memory for a *memory* framebuffer. - /// Other framebuffer types are not allowed to be passed. - syscall get_framebuffer_memory { - in fb: Framebuffer; - out memory: VideoMemory; - error InvalidHandle; - error Unsupported; + /// A structure describing an input device. + struct DeviceDescriptor { + field class: DeviceClass; + field protocol: DeviceProtocol; + field capabilities: DeviceCapabilities; + + /// Number of relative analog axes. + /// + /// NOTE: This includes axes like accelerometer axes, which have zero + /// output at rest, and only emit data when changed. + /// Same rate of change = Same value. + field rel_axes_cnt: u16; + + /// Number of absolute analog axes. + /// + /// NOTE: The value for these axes will be normalized by the kernel. + /// + /// NOTE: This includes axes like joysticks which have a zero position, + /// only change their reported value in a reproducible manner. + /// Same position = Same value. + field abs_axes_cnt: u16; + + /// Number of non-keyboard digital buttons the device provides. + /// + /// NOTE: This includes buttons like A/B/X/Y or Start/Select. + field digital_button_count: u16; + + /// The vendor id of the device. + /// NOTE: This value shall be interpreted depending on `protocol`. + /// NOTE: `vendor_id` may be set to `0xFFFF` if not applicable. + field vendor_id: u16; + + /// The product id of the device. + /// NOTE: This value shall be interpreted depending on `protocol`. + /// NOTE: `product_id` may be set to `0xFFFF` if not applicable. + field product_id: u16; } - /// Marks a portion of the framebuffer as changed and forces the OS to - /// perform an update action if necessary. - syscall invalidate_framebuffer { - in fb: Framebuffer; - in area: Rectangle; + /// Queries metadata about an input device. + /// + /// If `name_buf` is `null`, no name is written but `name_len` is still returned. + /// If `unique_id_buf` is `null`, no unique id is written but `unique_id_len` is still returned. + /// + /// NOTE: `unique_id` is an optional, implementation-defined identifier that can be used by + /// applications to recognize devices again across hotplug. + /// + /// It may be empty if the kernel cannot provide one. + /// + syscall query_device_metadata { + in id: DeviceId; + in name_buf: ?[]u8; + in unique_id_buf: ?[]u8; + + /// If not `null`, the kernel will fill this structure with metadata for the device. + in descriptor: ?*DeviceDescriptor; + + out name_len: usize; + out unique_id_len: usize; + + /// `id` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; } - /// Renders the provided Ashet Graphics Protocol `sequence` into `target` framebuffer. + + /// Waits for the next event emitted by a specific device. /// - /// The function will run asynchronously and will return as soon as the rendering is done. + /// This operation is **edge-triggered**: + /// - it does not buffer events, + /// - it may be lossy under high pressure, + /// - if multiple events occur between yields, intermediate events may be missed. /// - /// NOTE: On machines without hardware acceleration, this syscall might be completed synchronously. - async_call Render { - /// The framebuffer which should be drawn to. - in target: Framebuffer; - /// The AGP code that defines the drawing. - in sequence: bytestr; - /// If the target framebuffer is invalidatable, it is automatically invalidated after the completion - /// of the command sequence, ensuring presentation of the contents. - /// - /// This is useful when painting into widgets or windows to ensure the window manager - /// actually sees the changes as soon as they are done, reducing graphics pipeline latency. - in auto_invalidate: bool; - error BadCode; - error InvalidHandle; + /// NOTE: Any number of concurrent `GetDeviceEvent` operations may be scheduled for the + /// same device; they will all complete with the same next event. + /// A subsequent device event requires re-scheduling a new `GetDeviceEvent`. + /// + /// NOTE: If the device is removed for a pending `GetDeviceEvent` operation, it is + /// aborted with `error.Cancelled`. + async_call GetDeviceEvent { + in device: DeviceId; + out event: InputEvent; + + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + + /// `device` is `DeviceId.synthetic`. + error BadDevice; } -} + /// Emits a synthetic event *as if a real device had emitted it*. + /// + /// This updates the internal device state immediately and completes pending `GetDeviceEvent` + /// operations for that device. + /// + /// The kernel sets: + /// - `InputEvent.device = device` + /// - `InputEvent.flags.synthetic = true` + /// - `InputEvent.timestamp = clock.now()` (implementation-defined moment during the syscall) + /// + /// NOTE: This operation does not depend on any input groups existing. + /// If the device is present in groups, the event is enqueued into those group queues. + syscall emit_device_event { + in device: DeviceId; + in payload: InputEventPayload; -namespace gui { - syscall register_widget_type { - in descriptor: *const WidgetDescriptor; - out handle: WidgetType; - error AlreadyRegistered; - error SystemResources; + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + + /// `device` is `DeviceId.synthetic`. + error BadDevice; } - /// Opens a message box popup window and prompts the user for response. - async_call ShowMessageBox { - in desktop: Desktop; - in message: str; - in caption: str; - in buttons: MessageBoxButtons; - in icon: MessageBoxIcon; - out result: MessageBoxResult; + /// Queries the current state of a device for a batch of `queries`. + /// + /// The kernel fills `queries[i].value` for each entry. + /// + /// NOTE: Unsupported `which` values produce a sane default (0 / centered). + syscall query_device_state { + in device: DeviceId; + in queries: []StateQuery; + + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + + /// `queries[i].what` contains an unknown value. + error InvalidValue; } - /// Spawns a new window. - syscall create_window { - in desktop: Desktop; - in title: str; - in min: Size; - in max: Size; - in startup: Size; - in flags: CreateWindowFlags; - out handle: Window; - error InvalidDimensions; - error InvalidHandle; + /// A userland-owned input event queue which merges events from 0..n devices. + /// + /// The queue length is fixed at creation time. + /// + /// NOTE: An input group with zero devices may still receive events through + /// synthetic event injection. + /// + /// NOTE: If a device is removed, it is implicitly removed from all groups. + resource InputGroup { } + + /// Creates a new input group with a fixed event queue size. + /// + /// NOTE: The queue is bounded. If it becomes full, the kernel will drop the + /// oldest queued events so the newest events are kept intact. + /// + /// NOTE: Dropped events are reported through `GetEvent.dropped_since_last`. + syscall create_group { + /// Maximum number of events buffered by this group. + /// + /// NOTE: If `queue_size` is zero, an error will be returned. + in queue_size: usize; + + out group: InputGroup; + + /// `queue_size` is zero. + error InvalidValue; + error SystemResources; } - syscall get_window_title { - in window: Window; - in title_buf: ?[]u8; - out title_len: usize; - error InvalidHandle; - } + /// Adds a device to an input group. + /// + /// NOTE: A device can participate in 0..n groups at the same time. + /// Each emitted device event is enqueued once into each group that contains the device. + syscall add_device { + in group: InputGroup; + in device: DeviceId; - syscall get_window_size { - in window: Window; - out size: Size; + /// `group` is not a valid input group resource. error InvalidHandle; - } - syscall get_window_min_size { - in window: Window; - out min_size: Size; - error InvalidHandle; + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; } - syscall get_window_max_size { - in window: Window; - out max_size: Size; - error InvalidHandle; - } + /// Removes a device from an input group. + /// + /// NOTE: Removing a device does not purge already queued events originating from that device. + /// Those events remain ordered relative to all other queued events. + syscall remove_device { + in group: InputGroup; + in device: DeviceId; - syscall get_window_flags { - in window: Window; - out flags: WindowFlags; + /// `group` is not a valid input group resource. error InvalidHandle; - } - /// Sets the `size` of `window` and returns the new actual size. - /// NOTE: This event is meant to be used from desktop APIs and will not automatically - /// notify the window of the resize event. - syscall set_window_size { - in window: Window; - in size: Size; - out actual_size: Size; - error InvalidHandle; + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; } - /// Resizes a window to the new size. - syscall resize_window { - in window: Window; - in size: Size; - error InvalidHandle; - } - /// Changes a window title. - syscall set_window_title { - in handle: Window; - in title: str; + /// Enumerates the devices that are currently part of an input group. + /// + /// If `devices` is `null`, the total number of devices in the group is returned; + /// otherwise, up to `devices.len` elements are written into the provided array. + syscall enumerate_group_devices { + in group: InputGroup; + in devices: ?[]DeviceId; + out count: usize; + + /// `group` is not a valid input group resource. error InvalidHandle; } - /// Notifies the desktop that a window wants attention from the user. - /// This could just pop the window to the front, make it blink, show a small notification, ... - syscall mark_window_urgent { - in handle: Window; - error InvalidHandle; - } + /// Enumerates the input groups that currently contain the given device. + /// + /// If `groups` is `null`, the total number of groups containing the device is returned; + /// otherwise, up to `groups.len` elements are written into the provided array. + /// + /// NOTE: This returns only groups that are visible to the calling process. + /// + /// NOTE: The group resources returned in `groups` will be bound to the calling process with + /// the `at_least_weak` bind operation to ensure access. + syscall enumerate_device_groups { + in device: DeviceId; + in groups: ?[]InputGroup; + out count: usize; - /// Waits for an event on the given `Window`, completing as soon as - /// an event arrived. - async_call GetWindowEvent { - in window: Window; - out event: WindowEvent; - error Cancelled; - error InProgress; - error InvalidHandle; - } + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; - /// Create a new widget identified by `uuid` on the given `window`. - /// Position and size of the widget are undetermined at start and a call to `place_widget` should be performed on success. - syscall create_widget { - in window: Window; - in uuid: *const UUID; - out widget: Widget; error SystemResources; - error WidgetNotFound; - error InvalidHandle; } - /// Moves and resizes a widget in one. + /// Waits for the next queued event from an input group. /// - /// NOTE: The position of a widget is unrestricted, but it's size - /// may be restricted by the selected widget type. - syscall place_widget { - in widget: Widget; - - /// The desired position and size of the widget. - in desired: Rectangle; - - /// The actual position and size of the widget after the operation. - out actual: Rectangle; + /// The operation completes when: + /// - the group queue is non-empty, or + /// - a new event arrives for the group. + /// + /// NOTE: If events are already available, this operation completes immediately. + /// + /// NOTE: Only a single `GetEvent` operation may be scheduled per group at a time. + /// This enforces strict, non-duplicating consumption and preserves ordering. + /// + /// NOTE: The group maintains a drop counter which increments whenever the queue is full + /// and an event must be dropped. + /// Each completion returns the number of dropped events since the last successful + /// dequeue/completion and resets that counter to zero. + /// + /// LORE: The queue is "newest-wins": on overflow, oldest events are discarded so the most + /// recent user input remains available. + /// + /// LORE: The group state is updated only when an event *leaves* the queue: + /// - a regular head-pop (returned by `GetEvent`), or + /// - an overflow head-pop (dropped due to overflow). + /// Events that are merely queued do not affect group state. + /// + async_call GetEvent { + in group: InputGroup; - error InvalidHandle; - } + out event: InputEvent; - enum WidgetControlID : u32 { ... } + /// Number of events dropped since the last successful dequeue from this group. + out dropped_since_last: u32; - /// Triggers the `control` event of the widget with the given `message` as a payload. - syscall control_widget { - in widget: Widget; - in message: WidgetControlMessage; - error SystemResources; + /// `group` is not a valid input group handle. error InvalidHandle; - } - - enum WidgetNotifyID : u32 { ... } - /// Puts a `widget_notify` event into the event queue of the `Window` that owns `widget`. - /// The parameters are passed as a `WidgetNotifyEvent` to the event queue. - syscall notify_owner { - in widget: Widget; - in type: WidgetNotifyID; - in params: *const [4]usize; - error SystemResources; - error InvalidHandle; + /// A `GetEvent` operation for `group` is already scheduled. + error NonExclusiveAccess; } - /// Returns WidgetType-associated "opaque" data for this widget. + /// Pushes a synthetic event into a group. /// - /// This is meant as a convenience tool to store additional information per widget - /// like internal state and such. + /// The injected event is appended to the back of the group queue (same ordering rule as + /// device-emitted events). The kernel sets: + /// - `InputEvent.device = DeviceId.synthetic` + /// - `InputEvent.flags.synthetic = true` + /// - `InputEvent.timestamp = clock.now()` /// - /// The size of this must be known and cannot be queried. - syscall get_widget_data { - in widget: Widget; - out data: [*]align(16) u8; + /// NOTE: This operation is atomic: it either enqueues the event or returns an error. + /// + /// If `force` is `false`, the syscall fails with `Overflow` if enqueueing would drop + /// an event due to a full queue. + /// + /// If `force` is `true`, the syscall behaves like a hardware event with newest-wins overflow, + /// except it is marked synthetic. + syscall queue_event { + in group: InputGroup; + in payload: InputEventPayload; + in force: bool; + + /// `group` is not a valid input group resource. error InvalidHandle; - } - /// Returns the current location and size of the provided widget. - syscall get_widget_bounds { - in widget: Widget; - out bounds: Rectangle; - error InvalidHandle; + /// Returned when `force == false` and enqueueing would drop an existing queued event. + error Overflow; } - /// Creates a new desktop with the given name. - syscall create_desktop { - /// User-visible name of the desktop. - in name: str; - in descriptor: *const DesktopDescriptor; - out desktop: Desktop; - error SystemResources; - } + /// Queries the current fused/accumulated state of a group for a batch of `queries`. + /// + /// NOTE: The fused state includes only devices that can meaningfully contribute to the queried item. + /// Non-applicable devices are ignored. + /// + /// NOTE: Group state is updated only when events leave the queue (returned or dropped), + /// so userland state reconstruction from the event stream is equivalent unless events are dropped. + syscall query_group_state { + in group: InputGroup; + in queries: []StateQuery; - /// Returns the name of the provided desktop. - syscall get_desktop_name { - in desktop: Desktop; - in name_buf: ?[]u8; - out name_len: usize; + /// `group` is not a valid input group resource. error InvalidHandle; - } - - /// Enumerates all available desktops. - syscall enumerate_desktops { - in serverlist: ?[]Desktop; - out count: usize; - } - /// Returns all windows for a desktop handle. - syscall enumerate_desktop_windows { - in desktop: Desktop; - in window: ?[]Window; - out count: usize; - error InvalidHandle; + /// `queries[i].what` contains an unknown value. + error InvalidValue; } - /// Returns desktop-associated "opaque" data for this window. + /// A single state query item. /// - /// This is meant as a convenience tool to store additional information per window - /// like position on the screen, orientation, alignment, ... + /// The kernel reads `what` and `which`, and writes `value`. /// - /// The size of this must be known and cannot be queried. - syscall get_desktop_data { - in window: Window; - out data: [*]align(16) u8; - error InvalidHandle; - } - - /// Notifies the system that a message box was confirmed by the user. + /// NOTE: All values are returned as i16: + /// - Digital inputs: 0 (inactive) or 1 (active) for a device; for a group, the sum of all + /// pressed contributors (so >1 is possible). + /// - Absolute axes: normalized [-32767..32767] for a device; for a group, summed and clamped. /// - /// NOTE: This function is meant to be implemented by a desktop server. - /// Regular GUI applications should not use this function as they have no - /// access to a `MessageBoxEvent.RequestID`. - syscall notify_message_box { - /// The desktop that completed the message box. - in source: Desktop; - /// The request id that was passed in `MessageBoxEvent`. - in request_id: MessageBoxEvent.RequestID; - /// The resulting button which the user clicked. - in result: MessageBoxResult; - error BadRequestId; - error InvalidHandle; - } + struct StateQuery { + /// Defines what kind of input should be queried. + field what: Item; - /// Posts an event into the window event queue so the window owner - /// can handle the event. - syscall post_window_event { - in window: Window; - in event: WindowEvent; - error SystemResources; - error InvalidHandle; - } + /// Defines which instance of `what` should be queried. + field which: u16; - /// Sends a notification to the provided `desktop`. - syscall send_notification { - /// Where to show the notification? - in desktop: Desktop; - /// What text is displayed in the notification? - in message: str; - /// How urgent is the notification to the user? - in severity: NotificationSeverity; - error SystemResources; - error InvalidHandle; - } + /// Output value filled by the kernel. + field value: i16; - namespace clipboard { - /// Sets the contents of the clip board. - /// Takes a mime type as well as the value in the provided format. - syscall set { - in desktop: Desktop; - in mime: str; - in value: str; - error SystemResources; - } + /// Selects which state component is queried. + enum Item : u16 { + /// `which` is a `KeyUsageCode`. + item keyboard_key = 0; - /// Returns the current type present in the clipboard, if any. - syscall get_type { - in desktop: Desktop; - in type_buf: ?[]u8; - out type_len: usize; - error InvalidHandle; - } + /// `which` is a `MouseButton` value. + item mouse_button = 1; - /// Returns the current clipboard value as the provided mime type. - /// The os provides a conversion *if possible*, otherwise returns an error. - /// The returned memory for `value` is owned by the process and must be freed with `ashet.process.memory.release`. - syscall get_value { - in desktop: Desktop; - in mime: str; - out value: []const u8; - error InvalidHandle; - error SystemResources; - error ConversionFailed; - error ClipboardEmpty; + /// `which` is a per-device button index (matches `InputEvent.Button.button`). + item control_button = 2; + + /// `which` is a per-device absolute axis index (matches `InputEvent.AbsAxis.axis`). + item abs_axis = 3; + + /// Absolute pointer X (normalized i16). `which` must be zero. + item pointer_x = 4; + + /// Absolute pointer Y (normalized i16). `which` must be zero. + item pointer_y = 5; } } -} -namespace service { - /// Registers a new service `uuid` in the system. - /// Takes an array of function pointers that will be provided for IPC and a service name to be advertised. - syscall create { - in uuid: *const UUID; - in funcs: []const anyfnptr; - in name: str; - out svc: Service; - error AlreadyRegistered; - error SystemResources; - } + /// Flags attached to an input event. + bitstruct EventFlags : u16 { + /// Set for events that did not originate from a device driver. + field synthetic: bool; - /// Enumerates all registered services. - syscall enumerate { - in uuid: *const UUID; - in services: ?[]Service; - out count: usize; + reserve u15 = 0; } - /// Returns the name of the service. - syscall get_name { - in svc: Service; - in name_buf: ?[]u8; - out name_len: usize; - error InvalidHandle; - } + /// An input event as delivered to userland. + struct InputEvent { + /// The type of event that was emitted. + field type: Type; - /// Returns the process that created this service. - syscall get_process { - in svc: Service; - out process: Process; - error InvalidHandle; - } + /// Timestamp from the moment the kernel receives the event in its input subsystem. + /// + /// NOTE: Multiple events may share the same timestamp due to timer resolution and internal handling. + field timestamp: clock.Absolute; - /// Returns the functions registerd by the service. - syscall get_functions { - in svc: Service; - in funcs: ?[]anyfnptr; - out count: usize; - error InvalidHandle; - } -} + /// The originating device id or `DeviceId.synthetic` if none. + field device: DeviceId; -resource Service { } + /// Event flags. + field flags: EventFlags; -resource SharedMemory { } + /// The event payload. + field payload: Payload; -resource Pipe { } + enum Type : u16 { + item key_press = 0; + item key_release = 1; -resource Process { } + item mouse_rel_motion = 2; + item mouse_abs_motion = 3; + item mouse_button_press = 4; + item mouse_button_release = 5; + item mouse_wheel = 6; -resource Thread { } + item digital_button_press = 7; + item digital_button_release = 8; -resource TcpSocket { } + item rel_axis_motion = 9; + item abs_axis_motion = 10; -resource UdpSocket { } + //? TODO: touch_down, touch_up, touch_move -resource File { } + ... + } -resource Directory { } + union Payload { + field keyboard: Keyboard; -resource VideoOutput { } + field mouse_rel_motion: MouseRelMotion; + field mouse_abs_motion: MouseAbsMotion; + field mouse_button: MouseButton; + field mouse_wheel: MouseWheel; -resource Font { } + field digital_button: Button; -/// A framebuffer is something that can be drawn on. -resource Framebuffer { } + field rel_axis: RelAxis; + field abs_axis: AbsAxis; + } -resource Window { } + /// Relative motion delta in device units (implementation-defined). + /// + /// NOTE: Consecutive relative motion events may be fused inside group queues. + struct MouseRelMotion { + /// Relative position delta in the horizontal axis. + /// Positive values move to the right. + field dx: i16; + + /// Relative position delta in the vertical axis. + /// Positive values move downwards. + field dy: i16; + } -resource Widget { } + /// Absolute pointer position on each axis in normalized i16: + /// - -32767 == -1.0 + /// - 0 == 0.0 + /// - 32767 == +1.0 + /// + /// NOTE: Consecutive absolute motion events may be fused inside group queues. + struct MouseAbsMotion { + /// Absolute position in the horizontal axis. + /// `-32767` is the left edge, `32767` is the right edge. + field x: i16; + + /// Absolute position in the vertical axis. + /// `-32767` is the top edge, `32767` is the bottom edge. + field y: i16; + } -resource Desktop { } + struct MouseButton { + /// Which mouse button was pressed/released. + field button: input.MouseButton; + } -resource WidgetType { } + /// Wheel delta (implementation-defined units). + /// + /// NOTE: Consecutive wheel events in the same direction may be fused inside group queues. + struct MouseWheel { + field dx: i16; + field dy: i16; + } -resource SyncEvent { } + struct Keyboard { + /// The raw usage code for the key. Meaning depends on the layout; + /// kinda represents the physical position on the keyboard. + field usage: KeyUsageCode; -resource Mutex { } + /// If set, the pressed key combination has a mapping in the current + /// keyboard layout that produces text input. + /// + /// NOTE: This doesn't necessarily contain printable codes, but can also contain + /// combining characters like `U+0301` (Combining Acute Accent). + /// + /// NOTE: This isn't a true *composed* text input and cannot be directly used in a + /// text field or such. This is primarily meant to be passed into an input + /// method editor. + /// + /// NOTE: The lifetime of this pointer can be assumed valid until a keyboard layout + /// change is performed. + /// + /// LORE: This field isn't a perfect solution, but it's good enough for what we're trying to + /// achieve: International text input. + /// The idea of using combining characters for dead keys allows the IME to actually compose + /// a sequence of `U+0301` (Combining Acute Accent), `U+0041` (Latin Capital Letter A) to be composed + /// into `U+00C1` (Latin Capital Letter A With Acute) instead of emitting two codepoints. + /// + /// This method is flexible enough to be future-proof and extensible. + /// + field text: ?str; -/// The maximum number of bytes in a file system identifier name. -/// This is chosen to be a power of two, and long enough to accommodate -/// typical file system names: -/// - `SYS` -/// - `USB0` -/// - `USB10` -/// - `PF0` -/// - `CF7` -const max_fs_name_len = 8; + /// The modifier keys currently active + field modifiers: KeyboardModifiers; + } -/// The maximum number of bytes in a file system type name. -/// Chosen to be a power of two, and long enough to accomodate typical names: -/// - `FAT16` -/// - `FAT32` -/// - `exFAT` -/// - `NTFS` -/// - `ReiserFS` -/// - `ISO 9660` -/// - `btrfs` -/// - `AFFS` -const max_fs_type_len = 32; + /// A non-keyboard digital button event (e.g. gamepad button). + /// + /// NOTE: `button` is an implementation-defined per-device index. + struct Button { + /// Defines which digital button was pressed/released. + field button: u16; + } -/// The maximum number of bytes in a file name. -/// This is chosen to be a power of two, and reasonably long. -/// As some programs use sha256 checksums and 64 bytes are enough to store -/// a hex-encoded 256 bit sequence: -/// - `114ac2caf8fefad1116dbfb1bd68429f68e9e088b577c9b3f5a3ff0fe77ec886` -/// This should also enough for most reasonable file names in the wild. -const max_file_name_len = 120; + /// An absolute analog axis event (e.g. joystick axis). + /// + /// NOTE: `axis` is an implementation-defined per-device index. + /// NOTE: `value` uses normalized i16: + /// -32767 == -1.0, 0 == 0.0, 32767 == +1.0 + struct AbsAxis { + /// Defines which axis has changed. + field axis: u16; + field value: i16; + } -struct UUID { - field bytes: [16]u8; -} + /// A relative analog axis event (e.g. accelerometer axis). + /// + /// NOTE: `axis` is an implementation-defined per-device index. + /// + /// NOTE: Consecutive relative axis events in the same direction may be fused inside group queues. + struct RelAxis { + /// Defines which axis has changed. + field axis: u16; + field delta: i16; + } + } + enum MouseButton : u8 { + item none = 0; + item left = 1; + item right = 2; + item middle = 3; + item nav_previous = 4; + item nav_next = 5; + } -/// A date-and-time type encoding the time point in question as a -/// Unix timestamp in milliseconds -enum DateTime : i64 { - /// 1970-01-01 00:00 - item epoch = 0; - ... -} + /// Keyboard modifier state accompanying key events. + bitstruct KeyboardModifiers : u16 { + field shift: bool; + field alt: bool; + field ctrl: bool; + field gui: bool; + field shift_left: bool; + field shift_right: bool; + field ctrl_left: bool; + field ctrl_right: bool; + field alt_graph: bool; + field gui_left: bool; + field gui_right: bool; + reserve u5 = 0; + } -/// Time in nanoseconds since system startup. -enum Absolute : u64 { - item system_start = 0; - ... -} + /// + /// This is an enumeration of all well-known HID Keyboard/Keypad Page (0x07) usage codes for + /// keys. + /// + /// NOTE: These codes do not necessarily correlate with what's printed on the key, but what's + /// printed on the same location of a typical US layout keyboard. + /// Use key usage codes for when you're interested in the *location* of a key, not the + /// its semantic meaning. + /// For example, the typical `WASD` input scheme would be `ZQSD` on an AZERTY keyboard, but + /// the locations would be the same. + /// + /// LORE: This mapping was chosen as it's the most widespread standard key list. These codes + /// are directly produced by both USB and Bluetooth keyboards and don't require any translation + /// in these cases. Also HID is a widespread standard. + /// + /// NOTE: The notes in this enumeration are taken verbatim from + /// [HID Usage Tables, Version 1.6, Keyboard/Keypad Page (0x07)](https://usb.org/sites/default/files/hut1_6.pdf). + /// + enum KeyUsageCode : u16 { + //? 01 Keyboard ErrorRollOver + //? 02 Keyboard POSTFail + //? 03 Keyboard ErrorUndefined -/// A duration in nanoseconds. -enum Duration : u64 { ... } + /// Keyboard `a` and `A` + /// NOTE: Typically remapped for other languages in the host system. + item a = 0x04; + /// Keyboard `b` and `B` + item b = 0x05; -enum PipeMode : u8 { - /// Completes immediatly even if no elements could be processed. - item nonblocking = 0; - /// Returns when at least one element could be processed. - item at_least_one = 1; - /// Returns only when all elements are processed. - item all = 2; -} + /// Keyboard `c` and `C` + /// NOTE: Typically remapped for other languages in the host system. + item c = 0x06; -enum NotificationSeverity : u8 { - /// Important information that require immediate action - /// by the user. - /// - /// This should be handled with care and only for reall - /// urgent situations like low battery power or - /// unsufficient disk memory. - item attention = 0; + /// Keyboard `d` and `D` + item d = 0x07; - /// This is a regular user notification, which should be used - /// sparingly. - /// - /// Typical notifications of this kind are in the category of - /// "download completed", "video fully rendered" or similar. - item information = 128; + /// Keyboard `e` and `E` + item e = 0x08; - /// Silent notifications that might be informational, but do not - /// require attention by the user at all. - item whisper = 255; + /// Keyboard `f` and `F` + item f = 0x09; - ... -} + /// Keyboard `g` and `G` + item g = 0x0A; + /// Keyboard `h` and `H` + item h = 0x0B; -struct Await_Options { - field wait: Wait; - field thread_affinity: Thread_Affinity; + /// Keyboard `i` and `I` + item i = 0x0C; - enum Thread_Affinity : u8 { - /// Waits for ARCs scheduled from *any* thread in the current process. - item all_threads; + /// Keyboard `j` and `J` + item j = 0x0D; - /// Waits for ARCs scheduled from *this* thread. - item this_thread; - } + /// Keyboard `k` and `K` + item k = 0x0E; - enum Wait : u8 { - /// Don't wait for any additional calls to complete, just return - /// whatever was completed in the meantime. - item dont_block = 0; + /// Keyboard `l` and `L` + item l = 0x0F; - /// Wait for at least a single call to complete operation. - item wait_one = 1; + /// Keyboard `m` and `M` + /// NOTE: Typically remapped for other languages in the host system. + item m = 0x10; - /// Wait until all scheduled operations have completed. - /// - /// This will only wait so long until either - /// a) all scheduled ops are stored into the result array - /// or - /// b) the result array is full - /// - /// NOTE: If `thread_affinity` is `.all_threads`, other threads can still - /// schedule more operations and make this function block longer. - item wait_all = 2; - - } -} - -/// Index of the systems video outputs. -enum VideoOutputID : u8 { - /// The primary video output - item primary = 0; - ... -} - -enum FontType : u32 { - item bitmap = 0; - item vector = 1; - ... -} - -enum FramebufferType : u8 { - /// A pure in-memory frame buffer used for off-screen rendering. - item memory = 0; - - /// A video device backed frame buffer. Can be used to paint on a screen - /// directly. - item video = 1; - - /// A frame buffer provided by a window. These frame buffers - /// may hold additional semantic information. - item window = 2; - - /// A frame buffer provided by a user interface element. These frame buffers - /// may hold additional semantic information. - item widget = 3; -} - -enum MessageBoxIcon : u8 { - item information = 0; - item question = 1; - item warning = 2; - item @"error" = 3; -} - -enum LogLevel : u8 { - item critical = 0; - item err = 1; - item warn = 2; - item notice = 3; - item debug = 4; -} - -enum FileSystemId : u32 { - /// This is the file system which the os has bootet from. - item system = 0; - - /// the filesystem isn't valid. - item invalid = 0xFFFFFFFF; - - /// All other ids are unique file systems. - ... -} + /// Keyboard `n` and `N` + item n = 0x11; -bitstruct FileAttributes : u16 { - field directory: bool; - reserve u15 = 0; -} - -enum FileAccess : u8 { - item read_only = 0; - item write_only = 1; - item read_write = 2; -} - -enum FileMode : u8 { - /// opens file when it exists on disk - item open_existing = 0; - - /// creates file when it does not exist, or opens the file without truncation. - item open_always = 1; - - /// creates file when there is no file with that name - item create_new = 2; - - /// creates file when it does not exist, or opens the file and truncates it to zero length - item create_always = 3; -} - -/// -/// This is an enumeration of all well-known HID Keyboard/Keypad Page (0x07) usage codes for -/// keys. -/// -/// NOTE: These codes do not necessarily correlate with what's printed on the key, but what's -/// printed on the same location of a typical US layout keyboard. -/// Use key usage codes for when you're interested in the *location* of a key, not the -/// its semantic meaning. -/// For example, the typical `WASD` input scheme would be `ZQSD` on an AZERTY keyboard, but -/// the locations would be the same. -/// -/// LORE: This mapping was chosen as it's the most widespread standard key list. These codes -/// are directly produced by both USB and Bluetooth keyboards and don't require any translation -/// in these cases. Also HID is a widespread standard. -/// -/// NOTE: The notes in this enumeration are taken verbatim from -/// [HID Usage Tables, Version 1.6, Keyboard/Keypad Page (0x07)](https://usb.org/sites/default/files/hut1_6.pdf). -/// -enum KeyUsageCode : u16 { - //? 01 Keyboard ErrorRollOver - //? 02 Keyboard POSTFail - //? 03 Keyboard ErrorUndefined - - /// Keyboard `a` and `A` - /// NOTE: Typically remapped for other languages in the host system. - item a = 0x04; - - /// Keyboard `b` and `B` - item b = 0x05; + /// Keyboard `o` and `O` + /// NOTE: Typically remapped for other languages in the host system. + item o = 0x12; - /// Keyboard `c` and `C` - /// NOTE: Typically remapped for other languages in the host system. - item c = 0x06; + /// Keyboard `p` and `P` + /// NOTE: Typically remapped for other languages in the host system. + item p = 0x13; - /// Keyboard `d` and `D` - item d = 0x07; + /// Keyboard `q` and `Q` + /// NOTE: Typically remapped for other languages in the host system. + item q = 0x14; - /// Keyboard `e` and `E` - item e = 0x08; + /// Keyboard `r` and `R` + item r = 0x15; - /// Keyboard `f` and `F` - item f = 0x09; + /// Keyboard `s` and `S` + item s = 0x16; - /// Keyboard `g` and `G` - item g = 0x0A; + /// Keyboard `t` and `T` + item t = 0x17; - /// Keyboard `h` and `H` - item h = 0x0B; + /// Keyboard `u` and `U` + item u = 0x18; - /// Keyboard `i` and `I` - item i = 0x0C; + /// Keyboard `v` and `V` + item v = 0x19; - /// Keyboard `j` and `J` - item j = 0x0D; + /// Keyboard `w` and `W` + /// NOTE: Typically remapped for other languages in the host system. + item w = 0x1A; - /// Keyboard `k` and `K` - item k = 0x0E; + /// Keyboard `x` and `X` + /// NOTE: Typically remapped for other languages in the host system. + item x = 0x1B; - /// Keyboard `l` and `L` - item l = 0x0F; + /// Keyboard `y` and `Y` + /// NOTE: Typically remapped for other languages in the host system. + item y = 0x1C; - /// Keyboard `m` and `M` - /// NOTE: Typically remapped for other languages in the host system. - item m = 0x10; + /// Keyboard `z` and `Z` + /// NOTE: Typically remapped for other languages in the host system. + item z = 0x1D; - /// Keyboard `n` and `N` - item n = 0x11; - /// Keyboard `o` and `O` - /// NOTE: Typically remapped for other languages in the host system. - item o = 0x12; + /// Keyboard `1` and `!` + /// NOTE: Typically remapped for other languages in the host system. + item @"1" = 0x1E; - /// Keyboard `p` and `P` - /// NOTE: Typically remapped for other languages in the host system. - item p = 0x13; + /// Keyboard `2` and `@` + /// NOTE: Typically remapped for other languages in the host system. + item @"2" = 0x1F; - /// Keyboard `q` and `Q` - /// NOTE: Typically remapped for other languages in the host system. - item q = 0x14; + /// Keyboard `3` and `#` + /// NOTE: Typically remapped for other languages in the host system. + item @"3" = 0x20; - /// Keyboard `r` and `R` - item r = 0x15; + /// Keyboard `4` and `$` + /// NOTE: Typically remapped for other languages in the host system. + item @"4" = 0x21; - /// Keyboard `s` and `S` - item s = 0x16; + /// Keyboard `5` and `%` + /// NOTE: Typically remapped for other languages in the host system. + item @"5" = 0x22; - /// Keyboard `t` and `T` - item t = 0x17; + /// Keyboard `6` and `^` + /// NOTE: Typically remapped for other languages in the host system. + item @"6" = 0x23; - /// Keyboard `u` and `U` - item u = 0x18; + /// Keyboard `7` and `&` + /// NOTE: Typically remapped for other languages in the host system. + item @"7" = 0x24; - /// Keyboard `v` and `V` - item v = 0x19; + /// Keyboard `8` and `*` + /// NOTE: Typically remapped for other languages in the host system. + item @"8" = 0x25; - /// Keyboard `w` and `W` - /// NOTE: Typically remapped for other languages in the host system. - item w = 0x1A; + /// Keyboard `9` and `(` + /// NOTE: Typically remapped for other languages in the host system. + item @"9" = 0x26; - /// Keyboard `x` and `X` - /// NOTE: Typically remapped for other languages in the host system. - item x = 0x1B; + /// Keyboard `0` and `)` + /// NOTE: Typically remapped for other languages in the host system. + item @"0" = 0x27; - /// Keyboard `y` and `Y` - /// NOTE: Typically remapped for other languages in the host system. - item y = 0x1C; + /// Keyboard Return (ENTER) + item enter = 0x28; - /// Keyboard `z` and `Z` - /// NOTE: Typically remapped for other languages in the host system. - item z = 0x1D; + /// Keyboard ESCAPE + item escape = 0x29; + /// Keyboard DELETE (Backspace) + /// NOTE: Backs up the cursor one position, deleting a character as it goes. + item backspace = 0x2A; - /// Keyboard `1` and `!` - /// NOTE: Typically remapped for other languages in the host system. - item @"1" = 0x1E; + /// Keyboard Tab + item tab = 0x2B; - /// Keyboard `2` and `@` - /// NOTE: Typically remapped for other languages in the host system. - item @"2" = 0x1F; + /// Keyboard Spacebar + item space = 0x2C; - /// Keyboard `3` and `#` - /// NOTE: Typically remapped for other languages in the host system. - item @"3" = 0x20; + /// Keyboard `-` and `_` + item minus = 0x2D; - /// Keyboard `4` and `$` - /// NOTE: Typically remapped for other languages in the host system. - item @"4" = 0x21; + /// Keyboard `=` and `+` + /// NOTE: Typically remapped for other languages in the host system. + item equals = 0x2E; - /// Keyboard `5` and `%` - /// NOTE: Typically remapped for other languages in the host system. - item @"5" = 0x22; + /// Keyboard `[` and `{` + /// NOTE: Typically remapped for other languages in the host system. + item square_bracket_open = 0x2F; - /// Keyboard `6` and `∧` - /// NOTE: Typically remapped for other languages in the host system. - item @"6" = 0x23; + /// Keyboard `]` and `}` + /// NOTE: Typically remapped for other languages in the host system. + item square_bracket_close = 0x30; - /// Keyboard `7` and `&` - /// NOTE: Typically remapped for other languages in the host system. - item @"7" = 0x24; + /// Keyboard `\\` and `|` + /// NOTE: Typically remapped for other languages in the host system. + item backslash = 0x31; - /// Keyboard `8` and `*` - /// NOTE: Typically remapped for other languages in the host system. - item @"8" = 0x25; + /// Keyboard Non-US `#` and `~` + /// NOTE: Typical language mappings: + /// US: `\` `|` + /// Belg: `µ` `\`` `£` + /// French Canadian: `<` `}` `>` + /// Danish: `'` `*` + /// Dutch: `<` `>` + /// French: `*` `µ` + /// German: `#` `'` + /// Italian: `ù` `§` + /// LatinAmerica: `}` `\`` `]` + /// Norwegian: `,` `*` + /// Spain: `}` `Ç` + /// Swedish: `,` `*` + /// Swiss: `$`, `£` + /// UK: `#` `~` + item non_us_hash = 0x32; + + /// Keyboard `;` and `:` + /// NOTE: Typically remapped for other languages in the host system. + item semicolon = 0x33; + + /// Keyboard `'` and `"` + /// NOTE: Typically remapped for other languages in the host system. + item apostrophe = 0x34; + + /// Keyboard Grave Accent (`\``) and Tilde (`~`) + /// NOTE: Typically remapped for other languages in the host system. + item grave_accent = 0x35; + + /// Keyboard `,` and `<` + /// NOTE: Typically remapped for other languages in the host system. + item comma = 0x36; + + /// Keyboard `.` and `>` + /// NOTE: Typically remapped for other languages in the host system. + item period = 0x37; + + /// Keyboard `/` and `?` + /// NOTE: Typically remapped for other languages in the host system. + item slash = 0x38; + + /// Keyboard Caps Lock + /// NOTE: Implemented as a non-locking key; sent as member of an array. + item caps_lock = 0x39; + + /// Keyboard F1 + item f1 = 0x3A; + + /// Keyboard F2 + item f2 = 0x3B; + + /// Keyboard F3 + item f3 = 0x3C; + + /// Keyboard F4 + item f4 = 0x3D; + + /// Keyboard F5 + item f5 = 0x3E; + + /// Keyboard F6 + item f6 = 0x3F; + + /// Keyboard F7 + item f7 = 0x40; + + /// Keyboard F8 + item f8 = 0x41; + + /// Keyboard F9 + item f9 = 0x42; + + /// Keyboard F10 + item f10 = 0x43; + + /// Keyboard F11 + item f11 = 0x44; + + /// Keyboard F12 + item f12 = 0x45; + + /// Keyboard PrintScreen + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item print_screen = 0x46; + + /// Keyboard Scroll Lock + /// NOTE: Implemented as a non-locking key; sent as member of an array. + item scroll_lock = 0x47; + + /// Keyboard Pause + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item pause = 0x48; + + /// Keyboard Insert + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item insert = 0x49; + + /// Keyboard Home + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item home = 0x4A; + + /// Keyboard PageUp + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item page_up = 0x4B; - /// Keyboard `9` and `(` - /// NOTE: Typically remapped for other languages in the host system. - item @"9" = 0x26; + /// Keyboard Delete Forward + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + /// NOTE: Deletes one character without changing position. + item delete = 0x4C; + + /// Keyboard End + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item end = 0x4D; + + /// Keyboard PageDown + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item page_down = 0x4E; + + /// Keyboard RightArrow + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item right_arrow = 0x4F; + + /// Keyboard LeftArrow + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item left_arrow = 0x50; + + /// Keyboard DownArrow + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item down_arrow = 0x51; + + /// Keyboard UpArrow + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item up_arrow = 0x52; + + /// Keypad Num Lock and Clear + /// NOTE: Implemented as a non-locking key; sent as member of an array. + item num_lock = 0x53; + + /// Keypad `/` + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item kp_divide = 0x54; + + /// Keypad `*` + item kp_multiply = 0x55; + + /// Keypad `-` + item kp_subtract = 0x56; + + /// Keypad `+` + item kp_add = 0x57; + + /// Keypad ENTER + item kp_enter = 0x58; + + /// Keypad `1` and End + item kp_1 = 0x59; + + /// Keypad `2` and Down Arrow + item kp_2 = 0x5A; + + /// Keypad `3` and PageDn + item kp_3 = 0x5B; + + /// Keypad `4` and Left Arrow + item kp_4 = 0x5C; + + /// Keypad `5` + item kp_5 = 0x5D; + + /// Keypad `6` and Right Arrow + item kp_6 = 0x5E; + + /// Keypad `7` and Home + item kp_7 = 0x5F; - /// Keyboard `0` and `)` - /// NOTE: Typically remapped for other languages in the host system. - item @"0" = 0x27; + /// Keypad `8` and Up Arrow + item kp_8 = 0x60; - /// Keyboard Return (ENTER) - item enter = 0x28; + /// Keypad `9` and PageUp + item kp_9 = 0x61; - /// Keyboard ESCAPE - item escape = 0x29; + /// Keypad `0` and Insert + item kp_0 = 0x62; - /// Keyboard DELETE (Backspace) - /// NOTE: Backs up the cursor one position, deleting a character as it goes. - item backspace = 0x2A; + /// Keypad `.` and Delete + item kp_period = 0x63; - /// Keyboard Tab - item tab = 0x2B; + /// Keyboard Non-US `\\` and `|` + /// NOTE: Typical language mappings: + /// Belg: `<` `\` `>` + /// French Canadian: `<` `°` `>` + /// Danish: `<` `\` `>` + /// Dutch: `]` `|` `[` + /// French: `<` `>` + /// German: `<` `|` `>` + /// Italian: `<` `>` + /// Latin America: `<` `>` + /// Norwegian: `<` `>` + /// Spain: `<` `>` + /// Swedish: `<` `|` `>` + /// Swiss: `<` `>` + /// UK: `\` `|` + /// Brazil: `\` `|` + /// NOTE: Typically near the Left-Shift key in AT-102 implementations. + item non_us_backslash = 0x64; - /// Keyboard Spacebar - item space = 0x2C; + /// Keyboard Application + /// NOTE: Windows key for Windows 95, and Compose. + item application = 0x65; - /// Keyboard `-` and `_` - item minus = 0x2D; + /// Keyboard Power + item power = 0x66; - /// Keyboard `=` and `+` - /// NOTE: Typically remapped for other languages in the host system. - item equals = 0x2E; + /// Keypad `=` + item kp_equals = 0x67; - /// Keyboard `[` and `{` - /// NOTE: Typically remapped for other languages in the host system. - item square_bracket_open = 0x2F; + /// Keyboard F13 + item f13 = 0x68; - /// Keyboard `]` and `}` - /// NOTE: Typically remapped for other languages in the host system. - item square_bracket_close = 0x30; + /// Keyboard F14 + item f14 = 0x69; - /// Keyboard `\\` and `|` - /// NOTE: Typically remapped for other languages in the host system. - item backslash = 0x31; + /// Keyboard F15 + item f15 = 0x6A; - /// Keyboard Non-US `#` and `~` - /// NOTE: Typical language mappings: - /// US: `\` `|` - /// Belg: `µ` `\`` `£` - /// French Canadian: `<` `}` `>` - /// Danish: `'` `*` - /// Dutch: `<` `>` - /// French: `*` `µ` - /// German: `#` `'` - /// Italian: `ù` `§` - /// LatinAmerica: `}` `\`` `]` - /// Norwegian: `,` `*` - /// Spain: `}` `Ç` - /// Swedish: `,` `*` - /// Swiss: `$`, `£` - /// UK: `#` `~` - item non_us_hash = 0x32; - - /// Keyboard `;` and `:` - /// NOTE: Typically remapped for other languages in the host system. - item semicolon = 0x33; - - /// Keyboard `'` and `“` - /// NOTE: Typically remapped for other languages in the host system. - item apostrophe = 0x34; - - /// Keyboard Grave Accent (`^`) and Tilde (`~`) - /// NOTE: Typically remapped for other languages in the host system. - item grave_accent = 0x35; - - /// Keyboard `,` and `<` - /// NOTE: Typically remapped for other languages in the host system. - item comma = 0x36; - - /// Keyboard `.` and `>` - /// NOTE: Typically remapped for other languages in the host system. - item period = 0x37; - - /// Keyboard `/` and `?` - /// NOTE: Typically remapped for other languages in the host system. - item slash = 0x38; - - /// Keyboard Caps Lock - /// NOTE: Implemented as a non-locking key; sent as member of an array. - item caps_lock = 0x39; - - /// Keyboard F1 - item f1 = 0x3A; - - /// Keyboard F2 - item f2 = 0x3B; - - /// Keyboard F3 - item f3 = 0x3C; - - /// Keyboard F4 - item f4 = 0x3D; - - /// Keyboard F5 - item f5 = 0x3E; - - /// Keyboard F6 - item f6 = 0x3F; - - /// Keyboard F7 - item f7 = 0x40; - - /// Keyboard F8 - item f8 = 0x41; - - /// Keyboard F9 - item f9 = 0x42; - - /// Keyboard F10 - item f10 = 0x43; - - /// Keyboard F11 - item f11 = 0x44; - - /// Keyboard F12 - item f12 = 0x45; - - /// Keyboard PrintScreen - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item print_screen = 0x46; - - /// Keyboard Scroll Lock - /// NOTE: Implemented as a non-locking key; sent as member of an array. - item scroll_lock = 0x47; - - /// Keyboard Pause - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item pause = 0x48; - - /// Keyboard Insert - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item insert = 0x49; - - /// Keyboard Home - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item home = 0x4A; - - /// Keyboard PageUp - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item page_up = 0x4B; + /// Keyboard F16 + item f16 = 0x6B; - /// Keyboard Delete Forward - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - /// NOTE: Deletes one character without changing position. - item delete = 0x4C; - - /// Keyboard End - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item end = 0x4D; - - /// Keyboard PageDown - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item page_down = 0x4E; - - /// Keyboard RightArrow - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item right_arrow = 0x4F; - - /// Keyboard LeftArrow - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item left_arrow = 0x50; - - /// Keyboard DownArrow - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item down_arrow = 0x51; - - /// Keyboard UpArrow - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item up_arrow = 0x52; - - /// Keypad Num Lock and Clear - /// NOTE: Implemented as a non-locking key; sent as member of an array. - item num_lock = 0x53; - - /// Keypad `/` - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item kp_divide = 0x54; - - /// Keypad `*` - item kp_multiply = 0x55; - - /// Keypad `-` - item kp_subtract = 0x56; - - /// Keypad `+` - item kp_add = 0x57; - - /// Keypad ENTER - item kp_enter = 0x58; - - /// Keypad `1` and End - item kp_1 = 0x59; - - /// Keypad `2` and Down Arrow - item kp_2 = 0x5A; - - /// Keypad `3` and PageDn - item kp_3 = 0x5B; - - /// Keypad `4` and Left Arrow - item kp_4 = 0x5C; - - /// Keypad `5` - item kp_5 = 0x5D; - - /// Keypad `6` and Right Arrow - item kp_6 = 0x5E; - - /// Keypad `7` and Home - item kp_7 = 0x5F; + /// Keyboard F17 + item f17 = 0x6C; - /// Keypad `8` and Up Arrow - item kp_8 = 0x60; + /// Keyboard F18 + item f18 = 0x6D; - /// Keypad `9` and PageUp - item kp_9 = 0x61; + /// Keyboard F19 + item f19 = 0x6E; - /// Keypad `0` and Insert - item kp_0 = 0x62; + /// Keyboard F20 + item f20 = 0x6F; - /// Keypad `.` and Delete - item kp_period = 0x63; + /// Keyboard F21 + item f21 = 0x70; - /// Keyboard Non-US `\\` and `|` - /// NOTE: Typical language mappings: - /// Belg: `<` `\` `>` - /// French Canadian: `<` `°` `>` - /// Danish: `<` `\` `>` - /// Dutch: `]` `|` `[` - /// French: `<` `>` - /// German: `<` `|` `>` - /// Italian: `<` `>` - /// Latin America: `<` `>` - /// Norwegian: `<` `>` - /// Spain: `<` `>` - /// Swedish: `<` `|` `>` - /// Swiss: `<` `>` - /// UK: `\` `|` - /// Brazil: `\` `|` - /// NOTE: Typically near the Left-Shift key in AT-102 implementations. - item non_us_backslash = 0x64; + /// Keyboard F22 + item f22 = 0x71; - /// Keyboard Application - /// NOTE: Windows key for Windows 95, and Compose. - item application = 0x65; + /// Keyboard F23 + item f23 = 0x72; - /// Keyboard Power - item power = 0x66; + /// Keyboard F24 + item f24 = 0x73; - /// Keypad `=` - item kp_equals = 0x67; + /// Keyboard Execute + item execute = 0x74; - /// Keyboard F13 - item f13 = 0x68; + /// Keyboard Help + item help = 0x75; - /// Keyboard F14 - item f14 = 0x69; + /// Keyboard Menu + item menu = 0x76; - /// Keyboard F15 - item f15 = 0x6A; + /// Keyboard Select + item select = 0x77; - /// Keyboard F16 - item f16 = 0x6B; + /// Keyboard Stop + item stop = 0x78; - /// Keyboard F17 - item f17 = 0x6C; + /// Keyboard Again + item again = 0x79; - /// Keyboard F18 - item f18 = 0x6D; + /// Keyboard Undo + item undo = 0x7A; - /// Keyboard F19 - item f19 = 0x6E; + /// Keyboard Cut + item cut = 0x7B; - /// Keyboard F20 - item f20 = 0x6F; + /// Keyboard Copy + item copy = 0x7C; - /// Keyboard F21 - item f21 = 0x70; + /// Keyboard Paste + item paste = 0x7D; - /// Keyboard F22 - item f22 = 0x71; + /// Keyboard Find + item find = 0x7E; - /// Keyboard F23 - item f23 = 0x72; + /// Keyboard Mute + item mute = 0x7F; - /// Keyboard F24 - item f24 = 0x73; + /// Keyboard Volume Up + item volume_up = 0x80; - /// Keyboard Execute - item execute = 0x74; + /// Keyboard Volume Down + item volume_down = 0x81; - /// Keyboard Help - item help = 0x75; + /// Keyboard Locking Caps Lock + /// NOTE: Implemented as a locking key; sent as a toggle button. + /// Available for legacy support; however, most systems should use the non-locking version of this key + item locking_caps_lock = 0x82; - /// Keyboard Menu - item menu = 0x76; + /// Keyboard Locking Num Lock + /// NOTE: Implemented as a locking key; sent as a toggle button. + /// Available for legacy support; however, most systems should use the non-locking version of this key + item locking_num_lock = 0x83; - /// Keyboard Select - item select = 0x77; + /// Keyboard Locking Scroll Lock + /// NOTE: Implemented as a locking key; sent as a toggle button. + /// Available for legacy support; however, most systems should use the non-locking version of this key + item locking_scroll_lock = 0x84; - /// Keyboard Stop - item stop = 0x78; + /// Keypad Comma + /// NOTE: Keypad Comma is the appropriate usage for the Brazilian keypad period (`.`) key. + /// This represents the closest possible match, and system software should do the correct + /// mapping based on the current locale setting. + item kp_comma = 0x85; - /// Keyboard Again - item again = 0x79; + /// Keypad Equal Sign + /// NOTE: Used on AS/400 keyboards. + item kp_equals_as400 = 0x86; - /// Keyboard Undo - item undo = 0x7A; - /// Keyboard Cut - item cut = 0x7B; + /// Keyboard International1 + /// NOTE: Keyboard International1 should be identified via footnote as the appropriate usage for the Brazilian + /// forward-slash (`/`) and question-mark (`?`) key. + /// This usage should also be renamed to either "Keyboard Non-US `/` and `?`" or to "Keyboard International1" + /// now that it's become clear that it does not only apply to Kanji keyboards anymore. + item international1 = 0x87; - /// Keyboard Copy - item copy = 0x7C; + /// Keyboard International2 + item international2 = 0x88; - /// Keyboard Paste - item paste = 0x7D; + /// Keyboard International3 + item international3 = 0x89; - /// Keyboard Find - item find = 0x7E; + /// Keyboard International4 + item international4 = 0x8A; - /// Keyboard Mute - item mute = 0x7F; + /// Keyboard International5 + item international5 = 0x8B; - /// Keyboard Volume Up - item volume_up = 0x80; + /// Keyboard International6 + item international6 = 0x8C; - /// Keyboard Volume Down - item volume_down = 0x81; + /// Keyboard International7 + /// NOTE: Toggle Double-Byte/Single-Byte mode + item international7 = 0x8D; - /// Keyboard Locking Caps Lock - /// NOTE: Implemented as a locking key; sent as a toggle button. - /// Available for legacy support; however, most systems should use the non-locking version of this key - item locking_caps_lock = 0x82; + /// Keyboard International8 + /// NOTE: Undefined, available for other Front End Language Processors. + item international8 = 0x8E; - /// Keyboard Locking Num Lock - /// NOTE: Implemented as a locking key; sent as a toggle button. - /// Available for legacy support; however, most systems should use the non-locking version of this key - item locking_num_lock = 0x83; + /// Keyboard International9 + /// NOTE: Undefined, available for other Front End Language Processors. + item international9 = 0x8F; - /// Keyboard Locking Scroll Lock - /// NOTE: Implemented as a locking key; sent as a toggle button. - /// Available for legacy support; however, most systems should use the non-locking version of this key - item locking_scroll_lock = 0x84; + /// Keyboard LANG1 + /// NOTE: Hangul/English toggle key. This usage is used as an input method editor control key on a Korean language keyboard. + item lang1 = 0x90; - /// Keypad Comma - /// NOTE: Keypad Comma is the appropriate usage for the Brazilian keypad period (`.`) key. - /// This represents the closest possible match, and system software should do the correct - /// mapping based on the current locale setting. - item kp_comma = 0x85; + /// Keyboard LANG2 + /// NOTE: Hanja conversion key. This usage is used as an input method editor control key on a Korean language keyboard. + item lang2 = 0x91; - /// Keypad Equal Sign - /// NOTE: Used on AS/400 keyboards. - item kp_equals_as400 = 0x86; + /// Keyboard LANG3 + /// NOTE: Defines the Katakana key for Japanese USB word-processing keyboards. + item lang3 = 0x92; + /// Keyboard LANG4 + /// NOTE: Defines the Hiragana key for Japanese USB word-processing keyboards. + item lang4 = 0x93; - /// Keyboard International1 - /// NOTE: Keyboard International1 should be identified via footnote as the appropriate usage for the Brazilian - /// forward-slash (`/`) and question-mark (`?`) key. - /// This usage should also be renamed to either "Keyboard Non-US `/` and `?`" or to "Keyboard International1" - /// now that it's become clear that it does not only apply to Kanji keyboards anymore. - item international1 = 0x87; + /// Keyboard LANG5 + /// NOTE: Defines the Zenkaku/Hankaku key for Japanese USB word-processing keyboards. + item lang5 = 0x94; - /// Keyboard International2 - item international2 = 0x88; + /// Keyboard LANG6 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang6 = 0x95; - /// Keyboard International3 - item international3 = 0x89; + /// Keyboard LANG7 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang7 = 0x96; - /// Keyboard International4 - item international4 = 0x8A; + /// Keyboard LANG8 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang8 = 0x97; - /// Keyboard International5 - item international5 = 0x8B; + /// Keyboard LANG9 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang9 = 0x98; - /// Keyboard International6 - item international6 = 0x8C; - /// Keyboard International7 - /// NOTE: Toggle Double-Byte/Single-Byte mode - item international7 = 0x8D; - /// Keyboard International8 - /// NOTE: Undefined, available for other Front End Language Processors. - item international8 = 0x8E; + /// Keyboard Alternate Erase + /// NOTE: Example, Erase-Eaze™ key. + item alternate_erase = 0x99; - /// Keyboard International9 - /// NOTE: Undefined, available for other Front End Language Processors. - item international9 = 0x8F; + /// Keyboard SysReq/Attention + /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. + /// That is, a key does not send extra codes to compensate for the state of any Control, + /// Alt, Shift or Num Lock keys. + item term_sysreq_attention = 0x9A; - /// Keyboard LANG1 - /// NOTE: Hangul/English toggle key. This usage is used as an input method editor control key on a Korean language keyboard. - item lang1 = 0x90; + /// Keyboard Cancel + item term_cancel = 0x9B; - /// Keyboard LANG2 - /// NOTE: Hanja conversion key. This usage is used as an input method editor control key on a Korean language keyboard. - item lang2 = 0x91; + /// Keyboard Clear + item term_clear = 0x9C; - /// Keyboard LANG3 - /// NOTE: Defines the Katakana key for Japanese USB word-processing keyboards. - item lang3 = 0x92; + /// Keyboard Prior + item term_prior = 0x9D; - /// Keyboard LANG4 - /// NOTE: Defines the Hiragana key for Japanese USB word-processing keyboards. - item lang4 = 0x93; + /// Keyboard Return + item term_return = 0x9E; - /// Keyboard LANG5 - /// NOTE: Defines the Zenkaku/Hankaku key for Japanese USB word-processing keyboards. - item lang5 = 0x94; + /// Keyboard Separator + item term_separator = 0x9F; - /// Keyboard LANG6 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang6 = 0x95; + /// Keyboard Out + item term_out = 0xA0; - /// Keyboard LANG7 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang7 = 0x96; + /// Keyboard Oper + item term_oper = 0xA1; - /// Keyboard LANG8 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang8 = 0x97; + /// Keyboard Clear/Again + item term_clear_again = 0xA2; - /// Keyboard LANG9 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang9 = 0x98; + /// Keyboard CrSel/Props + item term_crsel_props = 0xA3; + /// Keyboard ExSel + item term_exsel = 0xA4; + /// Keypad `00` + item kp_double_0 = 0xB0; - /// Keyboard Alternate Erase - /// NOTE: Example, Erase-Eaze™ key. - item alternate_erase = 0x99; + /// Keypad `000` + item kp_triple_0 = 0xB1; - /// Keyboard SysReq/Attention - /// NOTE: Usage of keys is not modified by the state of the Control, Alt, Shift or Num Lock keys. - /// That is, a key does not send extra codes to compensate for the state of any Control, - /// Alt, Shift or Num Lock keys. - item term_sysreq_attention = 0x9A; + /// Thousands Separator + /// NOTE: The symbol displayed will depend on the current locale settings + /// of the operating system. For example, the US thousands separator would + /// be a comma, and the decimal separator would be a period. + item kp_thousands_sep = 0xB2; - /// Keyboard Cancel - item term_cancel = 0x9B; + /// Decimal Separator + /// NOTE: The symbol displayed will depend on the current locale settings + /// of the operating system. For example, the US thousands separator would + /// be a comma, and the decimal separator would be a period. + item kp_decimal_sep = 0xB3; - /// Keyboard Clear - item term_clear = 0x9C; + /// Currency Unit + /// NOTE: The symbol displayed will depend on the current locale settings of the operating system. + /// For example the US currency unit would be $ and the sub-unit would be ¢. + item kp_currency_unit = 0xB4; - /// Keyboard Prior - item term_prior = 0x9D; + /// Currency Sub-unit + /// NOTE: The symbol displayed will depend on the current locale settings of the operating system. + /// For example the US currency unit would be $ and the sub-unit would be ¢. + item kp_currency_subunit = 0xB5; - /// Keyboard Return - item term_return = 0x9E; + /// Keypad `(` + item kp_round_bracket_open = 0xB6; - /// Keyboard Separator - item term_separator = 0x9F; + /// Keypad `)` + item kp_round_bracket_close = 0xB7; - /// Keyboard Out - item term_out = 0xA0; + /// Keypad `{` + item kp_curly_bracket_open = 0xB8; - /// Keyboard Oper - item term_oper = 0xA1; + /// Keypad `}` + item kp_curly_bracket_close = 0xB9; - /// Keyboard Clear/Again - item term_clear_again = 0xA2; + /// Keypad Tab + item kp_tab = 0xBA; - /// Keyboard CrSel/Props - item term_crsel_props = 0xA3; + /// Keypad Backspace + item kp_backspace = 0xBB; - /// Keyboard ExSel - item term_exsel = 0xA4; + /// Keypad `A` + item kp_a = 0xBC; - /// Keypad `00` - item kp_double_0 = 0xB0; + /// Keypad `B` + item kp_b = 0xBD; - /// Keypad `000` - item kp_triple_0 = 0xB1; + /// Keypad `C` + item kp_c = 0xBE; - /// Thousands Separator - /// NOTE: The symbol displayed will depend on the current locale settings - /// of the operating system. For example, the US thousands separator would - /// be a comma, and the decimal separator would be a period. - item kp_thousands_sep = 0xB2; + /// Keypad `D` + item kp_d = 0xBF; - /// Decimal Separator - /// NOTE: The symbol displayed will depend on the current locale settings - /// of the operating system. For example, the US thousands separator would - /// be a comma, and the decimal separator would be a period. - item kp_decimal_sep = 0xB3; + /// Keypad `E` + item kp_e = 0xC0; - /// Currency Unit - /// NOTE: The symbol displayed will depend on the current locale settings of the operating system. - /// For example the US currency unit would be $ and the sub-unit would be ¢. - item kp_currency_unit = 0xB4; + /// Keypad `F` + item kp_f = 0xC1; - /// Currency Sub-unit - /// NOTE: The symbol displayed will depend on the current locale settings of the operating system. - /// For example the US currency unit would be $ and the sub-unit would be ¢. - item kp_currency_subunit = 0xB5; + /// Keypad XOR + item kp_logic_xor = 0xC2; - /// Keypad `(` - item kp_round_bracket_open = 0xB6; + /// Keypad `∧` + item kp_logic_and = 0xC3; - /// Keypad `)` - item kp_round_bracket_close = 0xB7; + /// Keypad % + item kp_percent = 0xC4; - /// Keypad `{` - item kp_curly_bracket_open = 0xB8; + /// Keypad `<` + item kp_less_than = 0xC5; - /// Keypad `}` - item kp_curly_bracket_close = 0xB9; + /// Keypad `>` + item kp_greater_than = 0xC6; - /// Keypad Tab - item kp_tab = 0xBA; + /// Keypad `&` + item kp_ampersand = 0xC7; - /// Keypad Backspace - item kp_backspace = 0xBB; + /// Keypad `&&` + item kp_double_ampersand = 0xC8; - /// Keypad `A` - item kp_a = 0xBC; + /// Keypad `|` + item kp_pipe = 0xC9; - /// Keypad `B` - item kp_b = 0xBD; + /// Keypad `||` + item kp_double_pipe = 0xCA; - /// Keypad `C` - item kp_c = 0xBE; + /// Keypad `:` + item kp_colon = 0xCB; - /// Keypad `D` - item kp_d = 0xBF; + /// Keypad `#` + item kp_hash = 0xCC; - /// Keypad `E` - item kp_e = 0xC0; + /// Keypad Space + item kp_space = 0xCD; - /// Keypad `F` - item kp_f = 0xC1; + /// Keypad `@` + item kp_at = 0xCE; - /// Keypad XOR - item kp_logic_xor = 0xC2; + /// Keypad `!` + item kp_exclamation = 0xCF; - /// Keypad `∧` - item kp_logic_and = 0xC3; + /// Keypad Memory Store + item kp_memory_store = 0xD0; - /// Keypad % - item kp_percent = 0xC4; + /// Keypad Memory Recall + item kp_memory_recall = 0xD1; - /// Keypad `<` - item kp_less_than = 0xC5; + /// Keypad Memory Clear + item kp_memory_clear = 0xD2; - /// Keypad `>` - item kp_greater_than = 0xC6; + /// Keypad Memory Add + item kp_memory_add = 0xD3; - /// Keypad `&` - item kp_ampersand = 0xC7; + /// Keypad Memory Subtract + item kp_memory_subtract = 0xD4; - /// Keypad `&&` - item kp_double_ampersand = 0xC8; + /// Keypad Memory Multiply + item kp_memory_multiply = 0xD5; - /// Keypad `|` - item kp_pipe = 0xC9; + /// Keypad Memory Divide + item kp_memory_divide = 0xD6; - /// Keypad `||` - item kp_double_pipe = 0xCA; + /// Keypad `+/-` + item kp_plus_minus = 0xD7; - /// Keypad `:` - item kp_colon = 0xCB; + /// Keypad Clear + item kp_clear = 0xD8; - /// Keypad `#` - item kp_hash = 0xCC; + /// Keypad Clear Entry + item kp_clear_entry = 0xD9; - /// Keypad Space - item kp_space = 0xCD; + /// Keypad Binary + item kp_binary = 0xDA; - /// Keypad `@` - item kp_at = 0xCE; + /// Keypad Octal + item kp_octal = 0xDB; - /// Keypad `!` - item kp_exlamation = 0xCF; + /// Keypad Decimal + item kp_decimal = 0xDC; - /// Keypad Memory Store - item kp_memory_store = 0xD0; + /// Keypad Hexadecimal + item kp_hexadecimal = 0xDD; - /// Keypad Memory Recall - item kp_memory_recall = 0xD1; + /// Keyboard Left Control + item left_control = 0xE0; - /// Keypad Memory Clear - item kp_memory_clear = 0xD2; + /// Keyboard Left Shift + item left_shift = 0xE1; - /// Keypad Memory Add - item kp_memory_add = 0xD3; + /// Keyboard Left Alt + item left_alt = 0xE2; - /// Keypad Memory Subtract - item kp_memory_subtract = 0xD4; + /// Keyboard Left GUI + /// NOTE: Windows key for Windows 95, and Compose. + /// NOTE: Windowing environment key, examples are Microsoft® LEFT WIN key, Macintosh® LEFT APPLE key, Sun® LEFT META key. + item left_gui = 0xE3; - /// Keypad Memory Multiply - item kp_memory_multiply = 0xD5; + /// Keyboard Right Control + item right_control = 0xE4; - /// Keypad Memory Divide - item kp_memory_divide = 0xD6; + /// Keyboard Right Shift + item right_shift = 0xE5; - /// Keypad `+/-` - item kp_plus_minus = 0xD7; + /// Keyboard Right Alt + item right_alt = 0xE6; - /// Keypad Clear - item kp_clear = 0xD8; - - /// Keypad Clear Entry - item kp_clear_entry = 0xD9; - - /// Keypad Binary - item kp_binary = 0xDA; - - /// Keypad Octal - item kp_octal = 0xDB; - - /// Keypad Decimal - item kp_decimal = 0xDC; - - /// Keypad Hexadecimal - item kp_hexadecimal = 0xDD; - - /// Keyboard Left Control - item left_control = 0xE0; - - /// Keyboard Left Shift - item left_shift = 0xE1; - - /// Keyboard Left Alt - item left_alt = 0xE2; - - /// Keyboard Left GUI - /// NOTE: Windows key for Windows 95, and Compose. - /// NOTE: Windowing environment key, examples are Microsoft® LEFT WIN key, Macintosh® LEFT APPLE key, Sun® LEFT META key. - item left_gui = 0xE3; - - /// Keyboard Right Control - item right_control = 0xE4; - - /// Keyboard Right Shift - item right_shift = 0xE5; - - /// Keyboard Right Alt - item right_alt = 0xE6; - - /// Keyboard Right GUI - /// NOTE: Windows key for Windows 95, and Compose. - /// NOTE: Windowing environment key, examples are Microsoft® RIGHT WIN key, Macintosh® RIGHT APPLE key, Sun® RIGHT META key. - item right_gui = 0xE7; - - ... -} + /// Keyboard Right GUI + /// NOTE: Windows key for Windows 95, and Compose. + /// NOTE: Windowing environment key, examples are Microsoft® RIGHT WIN key, Macintosh® RIGHT APPLE key, Sun® RIGHT META key. + item right_gui = 0xE7; -enum MouseButton : u8 { - item none = 0; - item left = 1; - item right = 2; - item middle = 3; - item nav_previous = 4; - item nav_next = 5; - item wheel_down = 6; - item wheel_up = 7; + ... + } } -struct SpawnProcessArg { - field type: Type; - field value: Value; - - enum Type : u8 { - item string = 0; - item @"resource" = 1; +//? TODO: Review this namespace. +namespace network { + enum IP_Type : u8 { + item ipv4; + item ipv6; } - union Value { - field text: String; - field @"resource": SystemResource; + struct IPv4 { + field addr: [4]u8 ; //? TODO: align(4) } - struct String { - field text: str; + struct IPv6 { + field addr: [16]u8; //? TODO: align(4) + field zone: u8; } -} - -bitstruct WindowFlags : u32 { - field popup: bool; - field resizable: bool; - reserve u30 = 0; -} - -bitstruct CreateWindowFlags : u32 { - field popup: bool = false; - reserve u31 = 0; -} - -typedef WidgetEventHandler = fnptr (WidgetType, Widget, *const WidgetEvent) void; - - -struct WidgetDescriptor { - field uuid: UUID; - - /// Number of bytes allocated in a Widget for this widget type. - /// See @ref gui.get_widget_data function for further information. - field data_size: usize; - - field flags: Flags; - - //? TODO: Fill this out - - //? Event Handlers: - field handle_event: WidgetEventHandler; - - bitstruct Flags : u32 { - /// If `true`, the user can focus this widget with the mouse or keyboard. - field focusable: bool; - - /// If `true`, the user is able to open a context menu on this. - field context_menu: bool; - - /// If `true`, this widget is able to receive events with the mouse. - /// If `false`, the widget is ignored in the position-to-widget resolution. - field hit_test_visible: bool; - - /// If `true`, the user is able to potentially drop data via Drag&Drop - /// on this widget. - field allow_drop: bool; - - /// If `true`, the user can copy/cut/paste data from/into this widget. - field clipboard_sensitive: bool; + struct IP { + field type: IP_Type; + field addr: AnyAddr; - reserve u27 = 0; + union AnyAddr { + field v4: IPv4; + field v6: IPv6; + } } -} - -struct WidgetControlMessage { - field event_type: WidgetEvent.Type; - - /// The widget-specific type of the control message. - /// Could be something like `get_property`, `set_property`, `set_text`, ... - field type: gui.WidgetControlID; - - /// Generic parameters that can be passed to the widget. - field params: [4]usize; -} - -struct WidgetNotifyEvent { - field event_type: WindowEvent.Type; - - field widget: Widget; - - /// The widget-specific type of event. - /// Could be something like `text_changed`, `clicked`, `checked_changed`, ... - field type: gui.WidgetNotifyID; - - /// Generic data associated with the event. - field data: [4]usize; -} - - -enum MessageBoxResult : u8 { - item ok = 0; - item cancel = 1; - item yes = 2; - item no = 3; - item abort = 4; - item retry = 5; - item continue = 6; - item ignore = 7; -} - -bitstruct MessageBoxButtons : u8 { - const ok: MessageBoxButtons = .{ .has_ok = true }; - const ok_cancel: MessageBoxButtons = .{ .has_ok = true, .has_cancel = true }; - const yes_no: MessageBoxButtons = .{ .has_yes = true, .has_no = true }; - const yes_no_cancel: MessageBoxButtons = .{ .has_yes = true, .has_no = true, .has_cancel = true }; - const retry_cancel: MessageBoxButtons = .{ .has_retry = true, .has_cancel = true }; - const abort_retry_ignore: MessageBoxButtons = .{ .has_abort = true, .has_retry = true, .has_ignore = true }; - - field has_ok: bool = false; - field has_cancel: bool = false; - field has_yes: bool = false; - field has_no: bool = false; - field has_abort: bool = false; - field has_retry: bool = false; - field has_continue: bool = false; - field has_ignore: bool = false; -} - -typedef DesktopEventHandler = fnptr (Desktop, *const DesktopEvent) void; - -struct DesktopDescriptor { - /// Number of bytes allocated in a Window for this desktop. - /// See @ref gui.get_desktop_data function for further information. - field window_data_size: usize; - - /// A function pointer to the event handler of a desktop. - /// The desktop will receive events via this function. - field handle_event: DesktopEventHandler; -} - -union DesktopEvent { - field event_type: Type; - field create_window: DesktopWindowEvent; - field destroy_window: DesktopWindowEvent; - field invalidate_window: DesktopWindowInvalidateEvent; + struct EndPoint { + field ip: IP; + field port: u16; + } - field show_notification: DesktopNotificationEvent; - field show_message_box: MessageBoxEvent; + namespace dns { + //? TODO + } - enum Type : u16 { - //? lifecycle management: + namespace udp { + resource UdpSocket { } - /// A window was created on this desktop. - item create_window = 0; + /// Creates a new UDP socket. + syscall create_socket { + out socket: UdpSocket; + error SystemResources; + } - /// A window was destroyed on this desktop. - item destroy_window = 1; + async_call Bind { + in socket: UdpSocket; + in bind_point: EndPoint; + out endpoint: EndPoint; + error IllegalArgument; + error AddressInUse; + error IllegalValue; + error InvalidHandle; + error SystemResources; + } - /// A window has been invalidated and must be drawn again. - item invalidate_window = 2; + async_call Connect { + in socket: UdpSocket; + in target: EndPoint; + error AlreadyConnected; + error AlreadyConnecting; + error AddressInUse; + error BufferError; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } - //? user interaction: + async_call Disconnect { + in socket: UdpSocket; + error InvalidHandle; + error NotConnected; + error SystemResources; + } - /// `send_notification` was called and the desktop user should display - /// a notification. - item show_notification = 3; + async_call Send { + in socket: UdpSocket; + in data: bytestr; + out bytes_sent: usize; + error BufferError; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error NotConnected; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } - /// `send_notification` was called and the desktop user should display - /// a notification. - item show_message_box = 4; + async_call SendTo { + in socket: UdpSocket; + in receiver: EndPoint; + in data: bytestr; + out bytes_sent: usize; + error BufferError; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } - ... + async_call ReceiveFrom { + in socket: UdpSocket; + in buffer: bytebuf; + out bytes_received: usize; + out sender: EndPoint; + error BufferError; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } } -} -struct DesktopWindowEvent { - field event_type: DesktopEvent.Type; - field window: Window; -} - -struct DesktopWindowInvalidateEvent { - field event_type: DesktopEvent.Type; - field window: Window; - field area: Rectangle; -} - -struct DesktopNotificationEvent { - field event_type: DesktopEvent.Type; - - /// The text of the notification. - field message: str; + namespace tcp { + resource TcpSocket { } - /// The severity/importance of the notification. - field severity: NotificationSeverity; -} + /// Creates a new TCP socket. + syscall create_socket { + out socket: TcpSocket; + error SystemResources; + } -struct MessageBoxEvent { - field event_type: DesktopEvent.Type; + async_call Bind { + in socket: TcpSocket; + in bind_point: EndPoint; + out bound_endpoint: EndPoint; + error AddressInUse; + error IllegalValue; + error InvalidHandle; + error SystemResources; + } - /// The desktop-specific request id that must be passed into - /// `notify_message_box` to finish the message box request. - field request_id: RequestID; + async_call Connect { + in socket: TcpSocket; + in target: EndPoint; + error AlreadyConnected; + error AlreadyConnecting; + error BufferError; + error ConnectionAborted; + error ConnectionClosed; + error ConnectionReset; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } - /// Content of the message box. - field message: str; - - /// Caption of the message box. - field caption: str; - - /// Which buttons are presented to the user? - field buttons: MessageBoxButtons; - - /// Which icon is shown? - field icon: MessageBoxIcon; + async_call Send { + in socket: TcpSocket; + in data: bytestr; + out bytes_sent: usize; + error BufferError; + error ConnectionAborted; + error ConnectionClosed; + error ConnectionReset; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error NotConnected; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } - enum RequestID : u16 { ... } + async_call Receive { + in socket: TcpSocket; + in buffer: bytebuf; + /// if true, will read until `buffer.len` bytes arrived. otherwise will read until the end of a single packet + in read_all: bool; + out bytes_received: usize; + error AlreadyConnected; + error AlreadyConnecting; + error BufferError; + error ConnectionAborted; + error ConnectionClosed; + error ConnectionReset; + error IllegalArgument; + error IllegalValue; + error InProgress; + error InvalidHandle; + error LowlevelInterfaceError; + error NotConnected; + error OutOfMemory; + error Routing; + error SystemResources; + error Timeout; + } + } } +/// A file or directory on Ashet OS can be named with any legal UTF-8 sequence +/// that does not contain `/` and `:`. It is recommended to only create file names +/// that are actually typeable on the operating system though. /// -/// An 8-bit color value with a specialized encoding suitable for embedding -/// a practical set of 256 colors. -/// -/// The color encoding is basically a HSV (hue, saturation, value) color with 8 bits, using -/// 3 bits for the hue, 3 bits for the value and 2 bits for the saturation. +/// File names are measured in bytes (not Unicode codepoints). A single path segment +/// must not exceed `fs.max_file_name_len` bytes. /// -/// Naively mapping out the values to the HSV values has two problems though: -/// 1. A value of 0 maps all colors to black, meaning that we would have 64 different -/// types of blacks, which all would encode have the rgb value `(0, 0, 0)`. -/// 2. A saturation of 0 maps all colors to gray, effectively ignoring the hue. -/// This creates the situation that in addition to having 64 blacks, we would also -/// have each gray tone 8 times, wasting even more encoding space. +/// There are some reserved file names: /// -/// To address these two problems, the color scheme uses a modified mapping: +/// - `.` is the "current directory" selector and does not add to the path. +/// - `..` is the "parent directory" selector and navigates up in the directory hierarchy if possible. /// -/// - `hue` is used without special interpretation. -/// - `value` maps to a range of `[1:8]` instead of `[0:7]`, allowing 8 different -/// values that are all not black. -/// - `saturation` is used without special interpretation except for zero: -/// If the `saturation` field is zero, `hue` and `value` are interpreted together as a 6 bit -/// integer storing the brightness of gray. +/// Paths: /// -/// This yields a color space which has the following properties: -/// -/// - 64 true gray levels ranging from black to white. -/// - 8 different hues (red, yellow, lime, green, cyan, blue, purple, magenta). -/// - 3 different levels of saturation for each non-gray color. -/// - black maps to `0x00` (but white does not map to `0xFF`). -/// -/// This means we have all 256 colors mapped to a distinct, meaningful color that still allows -/// programmatic conversion from and to the color without the need of a look-up table that -/// would require searching the correct color. +/// The filesystem kernel API only accepts *relative* paths. /// -/// NOTE: This color encoding shall be referred to as "Ashet HSV". +/// A kernel path is a UTF-8 string composed of a sequence of names separated by one or more `/`. /// -/// LORE: This color encoding was developed over the course of several days, playing around with -/// many different encodings. -/// The color encodings/palettes were tested on a diverse set of images, including game screenshots, -/// photographs, artificial images, vector graphics and so on. -/// -/// The "Ashet HSV" encoding showed the best visual matches for most pictures, allowing both visual -/// fidelity on the color side, but also allowing both bright and dark images to work really well. -/// -bitstruct Color : u8 { - const black: Color = .{ .hue = 0, .value = 0, .saturation = 0 }; - const white: Color = .{ .hue = 7, .value = 7, .saturation = 0 }; - const red: Color = .{ .hue = 0, .value = 7, .saturation = 3 }; - const yellow: Color = .{ .hue = 1, .value = 7, .saturation = 3 }; - const lime: Color = .{ .hue = 2, .value = 7, .saturation = 3 }; - const green: Color = .{ .hue = 3, .value = 7, .saturation = 3 }; - const cyan: Color = .{ .hue = 4, .value = 7, .saturation = 3 }; - const blue: Color = .{ .hue = 5, .value = 7, .saturation = 3 }; - const purple: Color = .{ .hue = 6, .value = 7, .saturation = 3 }; - const magenta: Color = .{ .hue = 7, .value = 7, .saturation = 3 }; +/// Syntax rules: +/// - The empty string `""` is invalid. +/// - Consecutive slashes (regex `/+`) will be compacted into a single `/`. +/// - A leading `/` is invalid. +/// - A trailing `/` is invalid. +/// - `.` is allowed and means "this directory". +/// - `..` is allowed and navigates to the parent directory. At filesystem root, `..` saturates. +/// +/// Here are some examples for valid paths: +/// - `example.txt` +/// - `docs/wiki.txt` +/// - `system/fonts/../config.ini` +/// +/// NOTE: Absolute paths and filesystem designators like `SYS:/foo` are userland concepts. +/// +/// The canonical format for absolute paths in userland is a filesystem name, followed by a `:/`, +/// then a regular path relative to the root directory of the filesystem. +/// +/// Examples: +/// - `SYS:/apps/editor/code` +/// - `USB0:/foo/../bar` (which is equivalent to `USB0:/bar`) +/// +/// +/// NOTE: The filesystem that is used to boot the OS from has an alias `SYS:` that +/// is always a legal way to address this file system. +/// +/// NOTE: Reserved names can never exist as actual directory entries. +/// +/// NOTE: There is a limit on how long a file/directory name can be, but there's no limit +/// on how long a path can be. +/// This means there is no implicit directory nesting limit. +/// +/// NOTE: A file system identifier uses the following rules for names: +/// - Allowed characters: `[A-Z0-9\.]` +/// - Must start with a letter. +/// - Must not end with `.`. +/// +/// Examples: +/// - `SYS` +/// - `USB0.2` +/// - `NFS1` +/// +/// NOTE: Overlapped operations scheduled against the same underlying filesystem object +/// are ordered deterministically (FIFO-equivalent semantics), even across multiple +/// system resources. +/// The kernel may perform safe internal optimizations as long as the observable +/// semantics match the scheduling order. +/// +/// NOTE: Filesystem operations are write-through. On successful completion of an +/// operation, the change is committed to the underlying storage. +namespace fs { + /// The maximum number of bytes in a file system identifier name. + /// + /// This is chosen to be a power of two, and long enough to accommodate + /// typical file system names: + /// - `SYS` + /// - `USB0` + /// - `USB10` + /// - `USB10.3` + /// - `PF0` + /// - `CF7` + const max_fs_name_len = 8; + + /// The maximum number of bytes in a file system type name. + /// + /// Chosen to be a power of two, and long enough to accomodate typical names: + /// - `FAT16` + /// - `FAT32` + /// - `exFAT` + /// - `NTFS` + /// - `ReiserFS` + /// - `ISO 9660` + /// - `btrfs` + /// - `AFFS` + const max_fs_type_len = 32; + + /// The maximum number of bytes in a file name. + /// + /// LORE: This was chosen based off a survey of my local file system + /// and checking what kind of files exists. + /// As some programs use sha256 checksums for file names and 64 bytes + /// are enough to store a hex-encoded 256 bit sequence (`114ac2caf8fefad1116dbfb1bd68429f68e9e088b577c9b3f5a3ff0fe77ec886`), + /// the initial choice was 64 byte. + /// + /// With the invention of Ashet FS, a 64 byte string was possible, but would've wasted + /// 56 bytes of padding space due to two 32 bit pointers inside the same file system node, + /// the limit was raised to 120 characters. + /// + /// With 120 characters, we're settled well for basically all realistic file names + /// encountered in the wild. + const max_file_name_len = 120; - /// The hue of the color, encoded as 0 = 0° (red), 7 = 315° (magenta). - field hue: u3; + /// Identifies a mounted filesystem instance known to the kernel. + /// + /// NOTE: File system ids are allocated in a monotonically increasing + /// way, and will be stable until a file system is unmounted/removed + /// from the kernel. + /// + /// NOTE: Except `system`, the enumeration order of file systems is unspecified + /// and the ids cannot be assumed stable between reboots. + enum FileSystemId : u32 { + /// The filesystem the OS booted from. + item system = 0; - /// The value of the color, with 0 = 12.5% brightness and 7 = 100% brightness. - field value: u3; + /// All other ids are unique file systems. + ... + } - /// The saturation of the color, encoded as 0 = desaturated, and 3 = fully saturated. + /// Enumerates all currently available filesystems. + syscall enumerate_filesystems { + /// If not `null`, will receive filesystem ids. + in list: ?[]FileSystemId; + + /// The number of ids written to `list`, or the total count if `list` is `null`. + out count: usize; + } + + /// Finds a filesystem by name. /// - /// NOTE: The value is encoded as the uppermost 2 bits, so a check if saturation is 0 can be - /// performed by doing a less-than operation interpreting the color as an integer. - field saturation: u2; + /// NOTE: Name matching is case-sensitive. + /// + /// NOTE: The passed `name` may include a trailing `:`. + syscall find_filesystem { + in name: str; - struct RGB888 { - field r: u8; - field g: u8; - field b: u8; + out id: FileSystemId; + + /// No filesystem exists with the given name. + error NotFound; + + /// `name` does not conform to the name rules for filesystems. + error InvalidName; } - /// 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian + struct FileSystemInfo { + /// System-unique id of this file system + field id: FileSystemId; + + /// Compressed infos about the file system + field flags: Flags; + + /// User-addressable file system identifier (e.g. `SYS`, `USB0`, ...). + /// + /// Encoding: + /// - UTF-8 + /// - NUL-padded (first NUL determines length, otherwise full array). + field name: [max_fs_name_len]u8; + + /// String identifier of a file system driver (e.g. `FAT32`, `NFS`, ...) + /// + /// Encoding: + /// - UTF-8 + /// - NUL-padded (first NUL determines length, otherwise full array). + field filesystem: [max_fs_type_len]u8; + + bitstruct Flags : u16 { + /// This is the system boot filesystem. + field system: bool; + + /// The file system can be removed by the user. + field removable: bool; + + /// The filesystem is immutable and cannot be modified. + field immutable: bool; + + reserve u13 = 0; + } + } + + /// Queries information about a filesystem. + syscall get_filesystem_info { + in fs_id: FileSystemId; + out info: FileSystemInfo; + + /// The given filesystem id does not exist. + error InvalidFileSystem; + + error SystemResources; + } + + //? TODO: a way to query free space / capacity (if you want userland UIs to show disk usage without filesystem-specific driver calls) + + /// A directory is a group of files and other directories in a file system. + resource Directory { } + + /// Opens a directory relative to the root of a filesystem. + async_call Mount { + in fs_id: FileSystemId; + + /// The directory path relative to the root of the filesystem. + /// + /// NOTE: Passing `"."` yields the root directory handle. + in path: str; + + out dir: Directory; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// The given filesystem id did not exist when scheduling the operation. + error InvalidFileSystem; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + /// The requested entry exists but is not a directory. + error NotADir; + + /// The underlying filesystem of `fs_id` was removed + /// during the creation of the directory. + error Gone; + + error SystemResources; + } + + /// Opens a directory relative to `start_dir`. + async_call OpenDir { + in start_dir: Directory; + + /// The directory path relative to `start_dir`. + in path: str; + + out dir: Directory; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// `start_dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `start_dir` was removed. + error Gone; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + /// The requested entry exists but is not a directory. + error NotADir; + + error SystemResources; + } + + /// Writes the name of a directory into `*name_out`. /// - /// Layed out as a `u32` encoding `0xAARRGGBB`. - enum ARGB8888 : u32 { ... } + /// NOTE: For the filesystem root directory, the returned name is `"."`. + /// + /// NOTE: This syscall does not require filesystem I/O. + syscall get_dir_name { + in dir: Directory; + in name_out: *FileName; - /// 32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + } + + /// Returns the filesystem id a directory resides on. /// - /// Layed out as a `u32` encoding `0xAABBGGRR`. - enum ABGR8888 : u32 { ... } -} + /// NOTE: This syscall does not require filesystem I/O. + syscall get_dir_filesystem { + in dir: Directory; + out fs_id: FileSystemId; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + } -union InputEvent { - field event_type: Type; - field mouse: MouseEvent; - field keyboard: KeyboardEvent; + /// Returns true if `dir` is the root directory of its filesystem. + /// + /// NOTE: This syscall does not require filesystem I/O. + syscall is_root_dir { + in dir: Directory; + out is_root: bool; - enum Type : u16 { - item key_press = 0; - item key_release = 1; + /// `dir` is not a valid directory resource. + error InvalidHandle; - item mouse_rel_motion = 2; - item mouse_abs_motion = 3; - item mouse_button_press = 4; - item mouse_button_release = 5; + /// The underlying filesystem of `dir` was removed. + error Gone; } -} -union WidgetEvent { - field event_type: Type; + /// A directory enumerator allows enumerating directory entries. + /// + /// Enumeration properties: + /// - The enumeration order is unspecified. + /// - The enumerator is not rewindable/resettable. + /// - The enumerator is not a snapshot. + /// + /// Determinism rule: + /// - Enumeration is deterministic under scheduling. + /// - The kernel tracks mutations and enumerators to ensure an entry is never returned twice. + /// - An enumerator may over-enumerate (return entries that are deleted later), + /// but must never under-enumerate (skip entries that exist). + resource DirEnumerator { } + + /// Information about a filesystem entry (file or directory). + /// + /// NOTE: This structure intentionally does not include the entry name. + /// APIs that enumerate directory items accept an optional `FileName*` + /// output for the name. + struct FileInfo { + /// The size in bytes. + /// + /// NOTE: For directories, this value is always zero. + field size: u64; + + /// Timestamp of the creation time of the file. + /// + /// NOTE: Only valid when `flags.creation_date_valid` is true. + field creation_date: datetime.DateTime; + + /// Timestamp of the last modification time of the file. + /// + /// NOTE: Only valid when `flags.modified_date_valid` is true. + field modified_date: datetime.DateTime; + + /// Additional packed information. + field flags: Flags; + + enum FileType : u2 { + item file = 0; + item directory = 1; + } + + bitstruct Flags : u16 { + /// Entry type. + field type: FileType; + + /// `creation_date` is valid. + field creation_date_valid: bool; + + /// `modified_date` is valid. + field modified_date_valid: bool; + + reserve u12 = 0; + } + } + + /// A fixed-size file name buffer. + /// + /// The file name bytes are stored in `bytes[0..len]`. + /// Bytes beyond `len` are unspecified. + /// + /// NOTE: `len` is always `<= max_file_name_len`. + struct FileName { + field len: u8; + field bytes: [max_file_name_len]u8; + } + + /// Creates a directory enumerator for `dir`. + syscall create_enumerator { + in dir: Directory; + + out enumerator: DirEnumerator; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; - field mouse: MouseEvent; - field keyboard: KeyboardEvent; - field control: WidgetControlMessage; + error SystemResources; + } + + /// Returns information about the next entry in a directory enumeration. + /// + /// If `name_out` is not `null`, the kernel writes the entry name into `*name_out`. + /// The `name_out` pointer must remain valid until the operation completes. + async_call GetNextDirItem { + in enumerator: DirEnumerator; + + /// Optional output buffer for the entry name. + in name_out: ?*FileName; + + /// The information about the directory entry. + out info: FileInfo; + + /// Returned when the enumerator reached the end of the directory. + /// + /// NOTE: The `enumerator` resource should be destroyed after this error is + /// returned as there is no way to unwind an enumerator resource. + error EndOfDirectory; - //? TODO: Add event data + /// `enumerator` is not a valid enumerator resource. + error InvalidHandle; - enum Type : u16 { - //? lifecycle: + /// The underlying filesystem of `enumerator` was removed. + error Gone; - /// The widget was created and attached to a window. - item create = 0; + /// The underlying storage subsystem had an I/O failure. + error IoError; - /// The widget is in the process of being destroyed. - /// After this event, the handle will be invalid. - item destroy = 1; + error SystemResources; + } - /// The creator of the widget wants to do something widget-specific. - item control = 2; + /// Deletes a filesystem entry by path. + /// + /// Active handle rule: + /// - If the target (or any descendant when `recurse=true`) has an active handle + /// (`File`, `Directory`, `Location`, or `DirEnumerator`), the operation fails with `ActiveHandle`. + async_call Delete { + in dir: Directory; - //? basic input: + /// The path relative to `dir`, points to the file system entry that shall + /// be deleted. + in path: str; - /// The user clicked on the widget with the primary mouse button - /// or pressed the return or space bar button on the keyboard. + /// Defines if the operation should recursively delete a directory if + /// `path` points to a directory resource. + /// + /// - `false`: Deleting a non-empty directory fails with `DirectoryNotEmpty`. + /// - `true`: Directories are deleted recursively. + in recurse: bool; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// The target is a non-empty directory and `recurse` is false. + error DirectoryNotEmpty; + + /// The operation conflicts with existing active handles. + error ActiveHandle; + + /// The filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + /// Creates a new directory relative to `dir`. + async_call MkDir { + in dir: Directory; + + /// A path relative to `dir` which points to the directory that shall be created. + /// + /// NOTE: If `path` contains subdirectories, missing intermediate directories are created. + in path: str; + + /// If `true`, the operation will return a directory handle. + in mkopen: bool; + + /// Optional handle to the created directory. + /// + /// If `mkopen` is true, `new_dir` receives an opened handle to the created directory. + /// If `mkopen` is false, `new_dir` is returned as `null`. + out new_dir: ?Directory; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The target path was expected to be non-existent, but an entry exists. + error Exists; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// There is not enough free space on the filesystem. + error NoSpaceLeft; + + /// The filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + /// Queries a filesystem entry by path. + async_call StatEntry { + in dir: Directory; + in path: str; + out info: FileInfo; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + error SystemResources; + } + + /// Renames or moves an entry within the same filesystem. + /// + /// NOTE: This operation is logically atomic under scheduling: + /// operations scheduled after it observe the new name/location; + /// operations scheduled before it observe the old name/location. + /// + /// NOTE: This is a cheap operation and does not require the copying of data. + /// + /// NOTE: This operation does not support replacing an existing destination. + async_call NearMove { + /// The directory defining the base for both the source + /// and destination of the rename operation. + in dir: Directory; + + /// Path relative to `dir` that points to the current file or directory name. + in src_path: str; + + /// Path relative to `dir` that points to the new file or directory name. + in dst_path: str; + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The destination path was expected to be non-existent, but an entry exists. + error Exists; + + /// The source entry does not exist. + error FileNotFound; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + + /// A passed path is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// There is not enough free space on the filesystem (e.g. directory entry allocation). + error NoSpaceLeft; + + /// The filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + + /// Moves an entry between unrelated directories. + /// + /// Atomicity: + /// - The move is all-or-nothing unless an `IoError` prevents full atomicity. + /// - On failures like `NoSpaceLeft`, the kernel attempts to roll back changes. + /// + /// Active handle rule: + /// - The kernel checks for `ActiveHandle` recursively before starting the move. + /// - While the operation is active, moved entries are treated as not visible at the source. + /// - The destination becomes visible only on successful completion. + /// + /// NOTE: This operation can move between different filesystems and may copy data. + /// + /// NOTE: If `src_dir` and `dst_dir` are in the same file system, the kernel may perform + /// the move operation with the efficiency of `NearMove`, but the `ActiveHandle` checks + /// are still performed. + async_call FarMove { + /// The directory that defines the source file system. + in src_dir: Directory; + + /// Path relative to `src_dir` that defines which entry should be moved. + in src_path: str; + + /// The directory that defines the destination file system. + in dst_dir: Directory; + + /// Path relative to `dst_dir` that defines where the entry should be moved. + in dst_path: str; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The destination path was expected to be non-existent, but an entry exists. + error Exists; + + /// The source entry does not exist. + error FileNotFound; + + /// One of the directory handles is invalid. + error InvalidHandle; + + /// The underlying filesystem of a passed handle was removed. + error Gone; + + /// A passed path is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// The operation conflicts with existing active handles. + error ActiveHandle; + + /// There is not enough free space on the destination filesystem. + error NoSpaceLeft; + + /// A involved filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + /// Copies an entry between unrelated directories. + /// + /// NOTE: This operation can copy between different filesystems. + /// + /// Atomicity: + /// - The copy is all-or-nothing unless an `IoError` prevents full atomicity. + /// - On failures like `NoSpaceLeft`, the kernel attempts to roll back changes. + async_call Copy { + /// The directory that defines the base of `src_path`. + in src_dir: Directory; + + /// A path relative to `src_dir` that defines the entry which shall be copied. + in src_path: str; + + /// The directory that defines the base of `dst_path`. + in dst_dir: Directory; + + /// A path relative to `dst_dir` that defines the target of the copy operation. + in dst_path: str; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The destination path was expected to be non-existent, but an entry exists. + error Exists; + + /// The source entry does not exist. + error FileNotFound; + + /// One of the directory handles is invalid. + error InvalidHandle; + + /// The underlying filesystem of a passed handle was removed. + error Gone; + + /// A passed path is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// There is not enough free space on the destination filesystem. + error NoSpaceLeft; + + /// A involved filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + /// A file is a handle to a binary data storage that is stored + /// in a file system. + /// + /// NOTE: Files are byte-addressed and accessed by explicit offsets. + resource File { } + + enum FileAccess : u8 { + item read_only = 0; + item write_only = 1; + item read_write = 2; + } + + enum FileMode : u8 { + /// Opens file when it exists on disk + item open_existing = 0; + + /// Creates file when it does not exist, or opens the file without truncation. + item open_always = 1; + + /// Creates file when there is no file with that name + item create_new = 2; + + /// Creates file when it does not exist, or opens the file and truncates it to zero length + item create_always = 3; + } + + /// Opens a file relative to `dir`. + /// + /// NOTE: Opening a directory path as a file fails with `NotAFile`. + async_call OpenFile { + in dir: Directory; + in path: str; + + /// Defines what access to the file is desired. + in access: FileAccess; + + /// Defines how the open operation should handle non-existing/existing files. + in mode: FileMode; + + out handle: File; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// The target path was expected to be non-existent, but an entry exists. + error Exists; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// The requested entry exists but is not a file. + error NotAFile; + + /// There is not enough free space on the filesystem. + error NoSpaceLeft; + + /// The filesystem is immutable and cannot be modified. + /// + /// NOTE: This error is only returned when `access` requests write access. + error ImmutableFileSystem; + + error SystemResources; + } + + /// Writes the basename of a file into `*name_out`. + /// + /// NOTE: This syscall does not require filesystem I/O. + syscall get_file_name { + in file: File; + + in name_out: *FileName; + + /// `file` is not a valid file resource. + error InvalidHandle; + + /// The underlying filesystem of `file` was removed. + error Gone; + } + + /// Reads data from a file at `offset` into `buffer`. + /// + /// NOTE: Multiple `Read` and `Write` operations can be scheduled at the same time + /// and may complete concurrently. + async_call Read { + in file: File; + + /// The offset of the read operation in bytes from the beginning of the file. + in offset: u64; + + /// The buffer which shall receive the read data. + /// NOTE: `buffer` must stay valid until the operation completes. + in buffer: bytebuf; + + /// The number of bytes written to `buffer`. + /// + /// NOTE: This is only ever less than `buffer.len` if the + /// read operation would read over the end of the file. + /// + /// If that is the case, `count` is computed as: + /// `count = file_size -| offset`. + out count: usize; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// `file` is not a valid file resource. + error InvalidHandle; + + /// The underlying filesystem of `file` was removed. + error Gone; + + error SystemResources; + } + + /// Writes data to a file at `offset` from `buffer`. + /// + /// NOTE: Writes never extend a file. File growth is only possible via `Resize`. + /// + /// NOTE: Multiple `Read` and `Write` operations can be scheduled at the same time + /// and may complete concurrently. + async_call Write { + in file: File; + + /// The offset of the write operation in bytes from the beginning of the file. + in offset: u64; + + /// The data that shall be written to the file. + in buffer: bytestr; + + /// The number of bytes written to file. + /// + /// NOTE: This is only ever less than `buffer.len` if the + /// write operation would write over the end of the file. + /// + /// If that is the case, `count` is computed as: + /// `count = file_size -| offset`. + out count: usize; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// `file` is not a valid file resource. + error InvalidHandle; + + /// The underlying filesystem of `file` was removed. + error Gone; + + /// The file handle does not permit writing. + error WriteProtected; + + /// The filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + /// Queries information about an opened file. + async_call StatFile { + in file: File; + out info: FileInfo; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// `file` is not a valid file resource. + error InvalidHandle; + + /// The underlying filesystem of `file` was removed. + error Gone; + + error SystemResources; + } + + /// Resizes a file to `length` bytes. + /// + /// Growth properties: + /// - The filesystem must physically allocate storage (no sparse/overcommitted growth). + /// - Newly allocated bytes are zero-filled. + /// + /// Shrink properties: + /// - Shrinking is immediate and permanent. + /// - If the file is grown again later, the new bytes are zero. + /// + /// NOTE: Can be also used to truncate a file to zero length. + async_call Resize { + in file: File; + in length: u64; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// `file` is not a valid file resource. + error InvalidHandle; + + /// The underlying filesystem of `file` was removed. + error Gone; + + /// There is not enough free space on the filesystem. + error NoSpaceLeft; + + /// The file handle does not permit writing. + error WriteProtected; + + /// The filesystem is immutable and cannot be modified. + error ImmutableFileSystem; + + error SystemResources; + } + + /// A filesystem location is a `Directory` plus an associated relative path. + /// + /// This resource is used to transport a filesystem location across process boundaries, + /// including potentially non-existent targets (e.g. the output parameter inside Process arguments). + /// + /// A location can be opened/used similar to how a `(dir, filename)` pair + /// can be used. + /// + /// LORE: This type was introduced as a solution on how to pass file names + /// over a command line interface into an application. + /// As Ashet OS prefers relative paths to known directory handles over + /// absolute paths, a shell still needs the ability to pass non-existing + /// locations for parameters like `--output=…`. + /// Thus, the `Location` type was introduced which fuses a directory together + /// with a relative path. + resource Location { } + + /// Specifies how a `Location` should be interpreted by consumers. + enum LocationIntent : u8 { + /// The location may refer to a file or a directory. + item any = 0; + + /// The location should be treated as a file. + item file = 1; + + /// The location should be treated as a directory. + item directory = 2; + } + + /// Creates a new `Location` from `dir` and `path`. + /// + /// The stored path is normalized and syntactically resolved. + /// + /// NOTE: The path stored in a `Location` is normalized and syntactically resolved: + /// - Repeated separators are collapsed. + /// - `.` components are removed. + /// - `..` cancels a preceding non-`..` component (`x/../y` becomes `y`). + /// - If the resolved path would be empty, it is stored as `"."`. + /// + /// This is purely syntactical processing and does not touch the filesystem. + syscall create_location { + /// The directory that is the base of our location. + in dir: Directory; + + /// The path relative to `dir` which the location describes. + in path: str; + + /// The intent for the file system location. + /// + /// NOTE: This can be queried with `get_location_intent`. + in intent: LocationIntent; + + out loc: Location; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + + /// The given `path` is syntactically invalid. + error InvalidPath; + + error SystemResources; + } + + /// Returns the intent stored inside a `Location`. + syscall get_location_intent { + in loc: Location; + out intent: LocationIntent; + + /// `loc` is not a valid location resource. + error InvalidHandle; + + /// The underlying filesystem of `loc` was removed. + error Gone; + } + + /// Returns a clone of the base directory stored in a `Location`. + syscall get_location_dir { + in loc: Location; + out dir: Directory; + + /// `loc` is not a valid location resource. + error InvalidHandle; + + /// The underlying filesystem of `loc` was removed. + error Gone; + + error SystemResources; + } + + /// Returns the normalized, resolved path stored in a `Location`. + /// + /// NOTE: The returned string remains valid as long as `loc` is not destroyed. + syscall get_location_path { + in loc: Location; + out path: str; + + /// `loc` is not a valid location resource. + error InvalidHandle; + + /// The underlying filesystem of `loc` was removed. + error Gone; + } + + /// Opens the `Location` as a directory. + /// + /// NOTE: This is the `Location` variant of `OpenDir`. + async_call OpenAsDirectory { + in loc: Location; + out dir: Directory; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// `loc` is not a valid location resource. + error InvalidHandle; + + /// The underlying filesystem of `loc` was removed. + error Gone; + + /// The requested entry exists but is not a directory. + error NotADir; + + error SystemResources; + } + + /// Opens the `Location` as a file. + /// + /// NOTE: This is the `Location` variant of `OpenFile`. + async_call OpenAsFile { + in loc: Location; + in access: FileAccess; + in mode: FileMode; + out file: File; + + /// The underlying storage subsystem had an I/O failure. + error IoError; + + /// The requested entry does not exist. + error FileNotFound; + + /// The target path was expected to be non-existent, but an entry exists. + error Exists; + + /// `loc` is not a valid location resource. + error InvalidHandle; + + /// The underlying filesystem of `loc` was removed. + error Gone; + + /// The requested entry exists but is not a file. + error NotAFile; + + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + + /// There is not enough free space on the filesystem. + error NoSpaceLeft; + + /// The filesystem is immutable and cannot be modified. + /// + /// NOTE: This error is only returned when `access` requests write access. + error ImmutableFileSystem; + + error SystemResources; + } +} + +/// This namespace contains items related to shared memory objects. +namespace shm { + /// A shared memory object which, for its livetime, provides + /// a memory region which can be read and modified. + /// + /// NOTE: The memory region is valid until the resource is destroyed. + resource SharedMemory { } + + /// Constructs a new shared memory object with `size` bytes of memory. + /// Shared memory 1can be written without any memory protection. + /// + /// NOTE: The shared memory region will not be initialized by the kernel + /// so the content after creation is unspecified. + /// It should be set to the desired contents by the initial creator. + syscall create { + /// Number of bytes for the shared memory region. + /// The operation will fail when `0` is passed. + in size: usize; + + /// The created shared memory object. + out handle: SharedMemory; + + /// Returned when `size` is 0. + error InvalidSize; + + error SystemResources; + } + + /// Returns the memory region for the shared memory object. + /// + /// NOTE: The memory returned by this function is valid until the `handle` object is destroyed. + syscall get_memory { + in handle: SharedMemory; + + /// The memory region of the shared memory object. + out memory: []align(16) u8; + + /// The `handle` is not a valid shared memory object. + error InvalidHandle; + } +} + +/// This namespace contains items related to data pipes. +namespace pipe { + + /// A pipe is a two-ended, one-directional communication + /// channel which can either transport data streams or packets. + /// + /// Pipes can be synchronous or buffered: + /// - A synchronous pipe can only transfer data if a `Read` and a `Write` are active + /// at the same time. + /// - A buffered pipe has an internal memory which can store some elements + /// and makes `Read` and `Write` independent of each other. + /// + /// NOTE: Pipes will never transfer partial elements. + /// + /// NOTE: If multiple `Read` and `Write` operations are scheduled, the kernel + /// will process them in a FIFO manner. + /// This means that no interleaving between multiple `Write` operations will happen. + /// + /// NOTE: If a pipe is synchronous, a `Read` or `Write` operation can only complete + /// when a concurrent opposite operation is active. + /// The kernel will transfer the data directly from a `Write` operation into the + /// buffer of a `Read` operation without storing elements in kernel memory. + /// + /// NOTE: If a pipe is synchronous, and a `Read` uses `PipeMode.at_least_one`, it will + /// consume the maximum possible amount of elements from a single `Write`, but will + /// not merge data from multiple `Write` operations. + /// + /// NOTE: If a pipe is buffered, and a `Read` operation uses `PipeMode.at_least_one`, the + /// operation will consume a maximum of `fifo_length` elements, even if `Read.buffer` + /// could store more elements. + /// + /// NOTE: The `PipeMode` of a `Read` or `Write` operation only affects the operation itself + /// and will never affect other concurrently scheduled operations. + resource Pipe { } + + /// Creates a new pipe with `fifo_length` elements of `element_size` bytes. + /// If `fifo_length` is 0, the pipe is synchronous and can only send data + /// if a `Read` call is active. Otherwise, up to `fifo_length` elements can be + /// stored in a FIFO. + syscall create { + /// The size of the primitives in bytes the pipe operates on. Each element + /// transferred by the pipe has this size. + /// + /// NOTE: An elements size of 1 is making the pipe byte-oriented. + /// This can be mentally seen as data streaming instead of + /// packet oriented transmission. + /// + /// NOTE: An elements size of 0 is illegal and returns an error. + in element_size: usize; + + /// The number of elements that can be buffered inside the pipe before + /// making `Write` blocking. + /// + /// Passing 0 here makes the pipe a synchronous pipe, + /// any other value makes the pipe buffered. + in fifo_length: usize; + + /// The newly created pipe resource. + out handle: Pipe; + + error SystemResources; + + /// Returned when `element_size == 0`. + error InvalidSize; + } + + /// Returns the length of the pipe-internal FIFO in elements. + syscall get_fifo_length { + /// The pipe which should be queried. + in handle: Pipe; + + /// The length of the FIFO in elements. + out length: usize; + + /// `handle` is not a valid pipe resource. + error InvalidHandle; + } + + /// Returns the size of the elements stored in the pipe. + syscall get_element_size { + /// The pipe which should be queried. + in handle: Pipe; + + /// The size of the elements in bytes. + out size: usize; + + /// `handle` is not a valid pipe resource. + error InvalidHandle; + } + + enum PipeMode : u8 { + /// Completes immediately even if no elements could be processed. + /// NOTE: This means that `Read.count` or `Write.count` can be zero after completion. + item nonblocking = 0; + + /// Returns when at least one element could be processed. + /// NOTE: This means that `Read.count` or `Write.count` is at least one after completion + /// unless `data` or `buffer` do not hold a single element. + item at_least_one = 1; + + /// Returns only when all elements are processed. + /// NOTE: This means that `Read.count` or `Write.count` are at the maximum possible value + /// derived from `stride` and `data.len`/`buffer.len`. + item all = 2; + } + + /// Writes elements from `data` into the given pipe. + /// + /// NOTE: The number of elements inside `data` is computed by `(data.len - element_size + 1) / stride`. + async_call Write { + in handle: Pipe; + + /// Pointer to the first element. Length defines how many elements are to be transferred. + /// + /// NOTE: If `data.len < element_size`, the operation transfers 0 elements and completes + /// immediately. + in data: bytestr; + + /// Distance in bytes between each element in `data`. Can be different from the pipe's element + /// size to allow sparse data to be transferred. + /// + /// NOTE: If `0` is passed, `stride` will be set to the `element_size` property of the pipe. + /// + /// NOTE: It is legal to pass a `stride` smaller than `element_size`. This will copy elements + /// which are overlapping. + in stride: usize; + + /// Defines how the write should operate. + in mode: PipeMode; + + /// Number of elements written into the pipe. + out count: usize; + + /// `handle` is not a valid pipe resource. + error InvalidHandle; + } + + /// Reads elements from a pipe into `buffer`. + /// + /// NOTE: The max. number of elements written to `buffer` is computed by `(buffer.len - element_size + 1) / stride`. + async_call Read { + in handle: Pipe; + + /// Points to the first element to be received. + /// + /// NOTE: The kernel will only write chunks of `element_size` in steps of `stride` bytes. + /// It will not write any other part of the buffer. + /// + /// NOTE: `BufferSize` is returned if `buffer.len < element_size`. + in buffer: bytebuf; + + /// Distance between each element in `buffer`. Can be different from the pipe's element size + /// to allow sparse data to be transferred. + /// + /// NOTE: If `0` is passed, `stride` will be set to the `element_size` property of the pipe. + /// + /// NOTE: It is legal to pass a `stride` smaller than `element_size`. This will write elements + /// which are overlapping inside `buffer`. If this is the case, only the last element + /// written is complete. + in stride: usize; + + /// Defines how the read should operate. + in mode: PipeMode; + + /// Number of elements written to `buffer`. + out count: usize; + + /// `handle` is not a valid pipe resource. + error InvalidHandle; + + /// `buffer.len` is smaller than `element_size`. + error BufferSize; + } +} + +/// This namespace contains items related to synchronization between multiple threads. +namespace sync { + /// A mutex implements an object which can be locked and unlocked. + /// + /// NOTE: As Ashet OS is cooperatively scheduled, it is not necessary to guard an access/operation + /// with a mutex without a scheduler yield. + /// This means using a mutex is only sensible when access to a certain resource should be guarded + /// over scheduler yield points. + /// + /// LORE: In contrast to most other operating systems, a mutex in Ashet OS isn't tied + /// to a thread or a process, but is a regular system resource that can be passed + /// around and can be shared between several processes and threads. + /// + /// This means that the concept of a "recursive mutex" doesn't make sense, as a mutex + /// has no knowledge of the locking thread. + resource Mutex { } + + /// Creates a new mutex. + syscall create_mutex { + out mutex: Mutex; + + error SystemResources; + } + + /// Tries to lock a mutex and returns if it was successful. + syscall try_lock { + /// The mutex that shall be locked. + in mutex: Mutex; + + /// `true` if the lock was successful, `false` otherwise. + out is_locked: bool; + + /// `mutex` is not a valid mutex resource. + error InvalidHandle; + } + + /// Unlocks a mutex. + /// + /// Completes the oldest pending `Lock` operation if one exists. + syscall unlock { + in mutex: Mutex; + + /// `mutex` is not a valid mutex resource. + error InvalidHandle; + + //? TODO: Consider "NotLocked" error to make it possible to detect + //? programming errors + } + + /// Locks a mutex. Will complete once the mutex is locked. + async_call Lock { + /// The mutex that shall be locked. + in mutex: Mutex; + + /// `mutex` is not a valid mutex resource. + error InvalidHandle; + } + + /// A sync-event is an edge-triggered notification mechanism that + /// can synchronize multiple actors. + resource SyncEvent { } + + /// Creates a new `SyncEvent` object that can be used to synchronize + /// different processes. + syscall create_event { + /// The created SyncEvent resource. + out event: SyncEvent; + error SystemResources; + } + + /// Completes the oldest pending `WaitForEvent` operation waiting for the given event. + /// + /// NOTE: If currently no `WaitForEvent` operation is pending on `event`, + /// the notification is lost. + syscall notify_one { + /// The event that shall be notified. + in event: SyncEvent; + + /// `true` if the notification completed a pending `WaitForEvent` operation, otherwise `false`. + out received: bool; + + /// `event` is not a valid sync event. + error InvalidHandle; + } + + /// Completes all `WaitForEvent` operations waiting for the given event. + /// + /// NOTE: If currently no `WaitForEvent` operation is pending on `event`, + /// the notification is lost. + syscall notify_all { + /// The event that shall be notified. + in event: SyncEvent; + + /// The number of completed `WaitForEvent` operations. + /// + /// NOTE: If `received == 0`, the notification was lost. + out received: usize; + + /// `event` is not a valid sync event. + error InvalidHandle; + } + + /// Waits for the given `SyncEvent` to be notified. + async_call WaitForEvent { + /// The event which shall be awaited. + in event: SyncEvent; + + /// `event` is not a valid sync event. + error InvalidHandle; + } +} + +/// This namespace contains items related to graphics rendering. +namespace draw { + /// A font is required to render text and defines how + /// glyphs are drawn. + resource Font { } + + /// A framebuffer is something that can be drawn on. + resource Framebuffer { } + + enum FramebufferType : u8 { + /// A pure in-memory frame buffer used for off-screen rendering. + item memory = 0; + + /// A video device backed frame buffer. Can be used to paint on a screen + /// directly. + item video = 1; + + /// A frame buffer provided by a window. These frame buffers + /// may hold additional semantic information. + item window = 2; + + /// A frame buffer provided by a user interface element. These frame buffers + /// may hold additional semantic information. + item widget = 3; + } + + /// Returns the font for the given font name, if any. + /// + /// NOTE: System fonts are fonts that are either embedded in the kernel or + /// automatically loaded from the `SYS:/system/fonts` folder on + /// boot. + /// + /// NOTE: The returned resource can be unbound, but cannot be destroyed. + /// A `resources.destroy` operation will unbind the font resource from all + /// processes, effectively invalidating this userland handle. + /// + /// The underlying kernel resource won't be destroyed. + syscall get_system_font { + /// The name of the system font. + in font_name: str; + + //? TODO: Add a way to hint font sizes for vector fonts. + + /// The resource handle of the system font. + out handle: Font; + + /// No system font with the given name exists. + error FileNotFound; + + error SystemResources; + } + + /// Creates a new custom font from the given data. + syscall create_font { + /// The encoded font data for a bitmap or vector format. + /// + /// TODO: Specify which font formats are allowed. + in data: bytestr; + + //? TODO: Add a way to hint font sizes for vector fonts. + + /// A font resource that represents the font inside `data`. + out handle: Font; + + /// `data` does not encode a valid font. + error InvalidData; + + error SystemResources; + } + + /// Returns true if the given font is a system-owned font. + syscall is_system_font { + in font: Font; + + /// `true` if `font` is a system font resource, otherwise `false`. + out system_font: bool; + + /// `font` is not a valid font resource. + error InvalidHandle; + } + + /// Measures the size of a text string. + /// + /// NOTE: This function accepts strings using the LF line separator + /// and will return the height of all lines and the width of + /// the longest line. + syscall measure_text_size { + in font: Font; + in text: str; + out size: Size; + + /// `font` is not a valid font resource. + error InvalidHandle; + } + + /// Creates a new in-memory framebuffer that can be used for off-screen painting. + /// + /// NOTE: The contents of the newly created framebuffer are unspecified. + syscall create_memory_framebuffer { + /// The size of the created framebuffer in pixels. + in size: Size; + + out handle: Framebuffer; + + /// Returned when `size.width` or `size.height` are zero. + error InvalidSize; + + error SystemResources; + } + + /// Creates a new framebuffer based off a video output. Can be used to output pixels + /// to the screen. + /// + /// NOTE: The returned `handle` is destroyed automatically when `output` + /// is destroyed. + syscall create_video_framebuffer { + in output: video.VideoOutput; + out handle: Framebuffer; + + /// `output` is not a valid video output resource. + error InvalidHandle; + + error SystemResources; + } + + /// Creates a new framebuffer that allows painting into a GUI window. + /// + /// NOTE: The returned `handle` is destroyed automatically when `window` + /// is destroyed. + syscall create_window_framebuffer { + in window: gui.Window; + out handle: Framebuffer; + + /// `window` is not a valid window resource. + error InvalidHandle; + + error SystemResources; + } + + /// Creates a new framebuffer that allows painting into a widget. + /// + /// NOTE: The returned `handle` is destroyed automatically when `widget` + /// is destroyed. + syscall create_widget_framebuffer { + in widget: gui.Widget; + out handle: Framebuffer; + + /// `widget` is not a valid widget resource. + error InvalidHandle; + + error SystemResources; + } + + /// Returns the type of a framebuffer object. + syscall get_framebuffer_type { + in fb: Framebuffer; + + /// The type of framebuffer `fb` is. + out type: FramebufferType; + + /// `fb` is not a valid framebuffer resource. + error InvalidHandle; + } + + /// Returns the size of a framebuffer object. + syscall get_framebuffer_size { + in fb: Framebuffer; + + /// The size of the framebuffer in pixels. + out size: Size; + + /// `fb` is not a valid framebuffer resource. + error InvalidHandle; + } + + /// Returns the video memory for a memory framebuffer. + /// + /// NOTE: The returned memory is stable and valid until the `fb` is destroyed. + /// + /// NOTE: Any framebuffer except memory framebuffers cannot have + /// memory mappings. + syscall get_framebuffer_memory { + in fb: Framebuffer; + + /// The descriptor of the pixel memory that forms the contents of `fb`. + out memory: video.VideoMemory; + + /// `fb` is not a valid framebuffer resource. + error InvalidHandle; + + /// `fb` is not a framebuffer created with `create_memory_framebuffer`. + error Unsupported; + } + + /// Marks a portion of the framebuffer as changed and forces the OS to + /// perform an update action if necessary. + syscall invalidate_framebuffer { + in fb: Framebuffer; + + /// The area of the framebuffer that has changed. + /// + /// NOTE: `area` is limited to the actual bounds of the framebuffer. + /// + /// NOTE: If `area.width` or `area.height` are zero, nothing will be invalidated. + in area: Rectangle; + + /// `fb` is not a valid framebuffer resource. + error InvalidHandle; + } + + /// Renders the provided Ashet Graphics Protocol `sequence` into `target` framebuffer. + /// + /// The operation will complete when rendering is done. + /// + /// NOTE: On machines without hardware acceleration, this operation might be + /// completed synchronously. + async_call Render { + /// The framebuffer which should be drawn to. + in target: Framebuffer; + + /// The AGP code that defines the drawing. + /// + /// NOTE: The kernel will validate the code inside `overlapped.schedule` and + /// immediately complete the operation with `BadCode` if `sequence` + /// is not a valid AGP command sequence. + /// + /// NOTE: The kernel will create an ephemeral copy of the code inside `overlapped.schedule` + /// if the operation will not be completed immediately. + in sequence: bytestr; + + /// If the target framebuffer is invalidatable, it is automatically invalidated after the completion + /// of the command sequence, ensuring presentation of the contents. + /// + /// This is useful when painting into widgets or windows to ensure the window manager + /// actually sees the changes as soon as they are done, reducing graphics pipeline latency. + in auto_invalidate: bool; + + /// `sequence` is not a valid AGP command sequence. + error BadCode; + + /// `target` is not a valid framebuffer resource. + error InvalidHandle; + } +} + +//? TODO: Review this namespace. +namespace gui { + resource Window { } + + resource Widget { } + + resource Desktop { } + + resource WidgetType { } + + enum NotificationSeverity : u8 { + /// Important information that require immediate action + /// by the user. + /// + /// This should be handled with care and only for reall + /// urgent situations like low battery power or + /// unsufficient disk memory. + item attention = 0; + + /// This is a regular user notification, which should be used + /// sparingly. + /// + /// Typical notifications of this kind are in the category of + /// "download completed", "video fully rendered" or similar. + item information = 128; + + /// Silent notifications that might be informational, but do not + /// require attention by the user at all. + item whisper = 255; + + ... + } + + enum MessageBoxIcon : u8 { + item information = 0; + item question = 1; + item warning = 2; + item @"error" = 3; + } + + bitstruct WindowFlags : u32 { + field popup: bool; + field resizable: bool; + reserve u30 = 0; + } + + bitstruct CreateWindowFlags : u32 { + field popup: bool = false; + reserve u31 = 0; + } + + typedef WidgetEventHandler = fnptr (WidgetType, Widget, *const WidgetEvent) void; + + struct WidgetDescriptor { + field uuid: UUID; + + /// Number of bytes allocated in a Widget for this widget type. + /// See @ref gui.get_widget_data function for further information. + field data_size: usize; + + field flags: Flags; + + //? TODO: Fill this out + + //? Event Handlers: + + field handle_event: WidgetEventHandler; + + bitstruct Flags : u32 { + /// If `true`, the user can focus this widget with the mouse or keyboard. + field focusable: bool; + + /// If `true`, the user is able to open a context menu on this. + field context_menu: bool; + + /// If `true`, this widget is able to receive events with the mouse. + /// If `false`, the widget is ignored in the position-to-widget resolution. + field hit_test_visible: bool; + + /// If `true`, the user is able to potentially drop data via Drag&Drop + /// on this widget. + field allow_drop: bool; + + /// If `true`, the user can copy/cut/paste data from/into this widget. + field clipboard_sensitive: bool; + + reserve u27 = 0; + } + } + + struct WidgetControlMessage { + field event_type: WidgetEvent.Type; + + /// The widget-specific type of the control message. + /// Could be something like `get_property`, `set_property`, `set_text`, ... + field type: gui.WidgetControlID; + + /// Generic parameters that can be passed to the widget. + field params: [4]usize; + } + + struct WidgetNotifyEvent { + field event_type: WindowEvent.Type; + + field widget: Widget; + + /// The widget-specific type of event. + /// Could be something like `text_changed`, `clicked`, `checked_changed`, ... + field type: gui.WidgetNotifyID; + + /// Generic data associated with the event. + field data: [4]usize; + } + + + enum MessageBoxResult : u8 { + item ok = 0; + item cancel = 1; + item yes = 2; + item no = 3; + item abort = 4; + item retry = 5; + item continue = 6; + item ignore = 7; + } + + bitstruct MessageBoxButtons : u8 { + const ok: MessageBoxButtons = .{ .has_ok = true }; + const ok_cancel: MessageBoxButtons = .{ .has_ok = true, .has_cancel = true }; + const yes_no: MessageBoxButtons = .{ .has_yes = true, .has_no = true }; + const yes_no_cancel: MessageBoxButtons = .{ .has_yes = true, .has_no = true, .has_cancel = true }; + const retry_cancel: MessageBoxButtons = .{ .has_retry = true, .has_cancel = true }; + const abort_retry_ignore: MessageBoxButtons = .{ .has_abort = true, .has_retry = true, .has_ignore = true }; + + field has_ok: bool = false; + field has_cancel: bool = false; + field has_yes: bool = false; + field has_no: bool = false; + field has_abort: bool = false; + field has_retry: bool = false; + field has_continue: bool = false; + field has_ignore: bool = false; + } + + typedef DesktopEventHandler = fnptr (Desktop, *const DesktopEvent) void; + + struct DesktopDescriptor { + /// Number of bytes allocated in a Window for this desktop. + /// See @ref gui.get_desktop_data function for further information. + field window_data_size: usize; + + /// A function pointer to the event handler of a desktop. + /// The desktop will receive events via this function. + field handle_event: DesktopEventHandler; + } + + union DesktopEvent { + field event_type: Type; + + field create_window: DesktopWindowEvent; + field destroy_window: DesktopWindowEvent; + field invalidate_window: DesktopWindowInvalidateEvent; + + field show_notification: DesktopNotificationEvent; + field show_message_box: MessageBoxEvent; + + enum Type : u16 { + //? lifecycle management: + + /// A window was created on this desktop. + item create_window = 0; + + /// A window was destroyed on this desktop. + item destroy_window = 1; + + /// A window has been invalidated and must be drawn again. + item invalidate_window = 2; + + //? user interaction: + + /// `send_notification` was called and the desktop user should display + /// a notification. + item show_notification = 3; + + /// `send_notification` was called and the desktop user should display + /// a notification. + item show_message_box = 4; + + ... + } + } + + struct DesktopWindowEvent { + field event_type: DesktopEvent.Type; + field window: Window; + } + + struct DesktopWindowInvalidateEvent { + field event_type: DesktopEvent.Type; + field window: Window; + field area: Rectangle; + } + + struct DesktopNotificationEvent { + field event_type: DesktopEvent.Type; + + /// The text of the notification. + field message: str; + + /// The severity/importance of the notification. + field severity: NotificationSeverity; + } + + struct MessageBoxEvent { + field event_type: DesktopEvent.Type; + + /// The desktop-specific request id that must be passed into + /// `notify_message_box` to finish the message box request. + field request_id: RequestID; + + /// Content of the message box. + field message: str; + + /// Caption of the message box. + field caption: str; + + /// Which buttons are presented to the user? + field buttons: MessageBoxButtons; + + /// Which icon is shown? + field icon: MessageBoxIcon; + + enum RequestID : u16 { ... } + } + + union WidgetEvent { + field event_type: Type; + + field mouse: MouseEvent; + field keyboard: KeyboardEvent; + field control: WidgetControlMessage; + + //? TODO: Add event data + + enum Type : u16 { + //? lifecycle: + + /// The widget was created and attached to a window. + item create = 0; + + /// The widget is in the process of being destroyed. + /// After this event, the handle will be invalid. + item destroy = 1; + + /// The creator of the widget wants to do something widget-specific. + item control = 2; + + //? basic input: + + /// The user clicked on the widget with the primary mouse button + /// or pressed the return or space bar button on the keyboard. + /// + /// NOTE: A click with the mouse is valid when, and only when: + /// `mouse_button_down` and `mouse_button_up` with the left mouse button happen on the + /// same widget. The hovered widget *may* change in between the mouse down and mouse up, + /// but the click will still be recognized. + /// NOTE: A click with the keyboard is valid when, and only when: + /// `key_press` and `key_release` happen without changing the focused widget, and only when + /// the focus giving key (space, return, ...) was pressed without any other key interrupting. + item click = 3; + + //? keyboard input: + + /// A key was pressed on the keyboard. + item key_press = 4; + + /// A key was released on the keyboard. + item key_release = 5; + + //? mouse specific extras: + + /// The mouse was moved inside the rectangle of the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item mouse_enter = 6; + + /// The mouse was moved outside the rectangle of the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item mouse_leave = 7; + + /// The mouse stopped for some time over the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item mouse_hover = 8; + + /// A mouse button was pressed over the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item mouse_button_press = 9; + + /// A mouse button was released over the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item mouse_button_release = 10; + + /// The mouse was moved over the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item mouse_motion = 11; + + /// A vertical or horizontal scroll wheel was scrolled over the widget. + /// + /// NOTE: This event can only happen when `hit_test_visible` was set + /// in the widget creation flags. + item scroll = 12; + + //? drag&drop operations: + + /// The user dragged a payload into the rectangle of this widget. + /// + /// NOTE: This event can only happen when `allow_drop` was set in the + /// widget creation flags. + item drag_enter = 13; + + /// The user dragged a payload out of the rectangle of this widget. + /// + /// NOTE: This event can only happen when `allow_drop` was set in the + /// widget type creation flags. + item drag_leave = 14; + + /// The user dragged a payload over the rectangle of this widget. + /// + /// NOTE: This event can only happen when `allow_drop` was set in the + /// widget type creation flags. + item drag_over = 15; + + /// The user dropped a payload into this widget. + /// + /// NOTE: This event can only happen when `allow_drop` was set in the + /// widget type creation flags. + item drag_drop = 16; + + //? clipboard operations: + + /// The user requested a clipboard copy operation, usually by pressing 'Ctrl-C'. + /// + /// NOTE: This event can only happen when `clipboard_sensitive` was set in + /// the widget type creation flags. + item clipboard_copy = 17; + + /// The user requested a clipboard paste operation, usually by pressing 'Ctrl-V'. + /// + /// NOTE: This event can only happen when `clipboard_sensitive` was set in + /// the widget type creation flags. + item clipboard_paste = 18; + + /// The user requested a clipboard cut operation, usually by pressing 'Ctrl-X'. + /// + /// NOTE: This event can only happen when `clipboard_sensitive` was set in + /// the widget type creation flags. + item clipboard_cut = 19; + + //? widget specific: + + //? TODO: Implement ResizedArgs with "desired size, actual size" + /// The widget was resized with a call to `place_widget`. + /// + /// NOTE: This event will not fire if the widget was only moved. + item resized = 21; + + /// The widget should draw itself. + item paint = 20; + + /// User pressed the "context menu" button or did a + /// secondary mouse button click on the widget. + item context_menu_request = 22; + + /// The widget received focus via mouse or keyboard. + item focus_enter = 23; + + /// The widget lost focus after receiving it. + item focus_leave = 24; + + ... + } + } + + union WindowEvent { + field event_type: Type; + + field mouse: MouseEvent; + field keyboard: KeyboardEvent; + field widget_notify: WidgetNotifyEvent; + + enum Type : u16 { + item widget_notify = 0; + + item key_press = 1; + item key_release = 2; + + item mouse_enter = 3; + item mouse_leave = 4; + item mouse_motion = 7; + item mouse_button_press = 6; + item mouse_button_release = 5; + + /// The user requested the window to be closed. + item window_close = 8; + + /// The window was minimized and is not visible anymore. + item window_minimize = 9; + + /// The window was restored from minimized state. + item window_restore = 10; + + /// The window is currently moving on the screen. Query `window.bounds` to get the new position. + item window_moving = 11; + + /// The window was moved on the screen. Query `window.bounds` to get the new position. + item window_moved = 12; + + /// The window size is currently changing. Query `window.bounds` to get the new size. + item window_resizing = 13; + + /// The window size changed. Query `window.bounds` to get the new size. + item window_resized = 14; + } + } + + syscall register_widget_type { + in descriptor: *const WidgetDescriptor; + out handle: WidgetType; + error AlreadyRegistered; + error SystemResources; + } + + + + + /// Opens a message box popup window and prompts the user for response. + async_call ShowMessageBox { + in desktop: Desktop; + in message: str; + in caption: str; + in buttons: MessageBoxButtons; + in icon: MessageBoxIcon; + out result: MessageBoxResult; + } + + /// Spawns a new window. + syscall create_window { + in desktop: Desktop; + in title: str; + in min: Size; + in max: Size; + in startup: Size; + in flags: CreateWindowFlags; + out handle: Window; + error InvalidDimensions; + error InvalidHandle; + error SystemResources; + } + + syscall get_window_title { + in window: Window; + in title_buf: ?[]u8; + out title_len: usize; + error InvalidHandle; + } + + syscall get_window_size { + in window: Window; + out size: Size; + error InvalidHandle; + } + + syscall get_window_min_size { + in window: Window; + out min_size: Size; + error InvalidHandle; + } + + syscall get_window_max_size { + in window: Window; + out max_size: Size; + error InvalidHandle; + } + + syscall get_window_flags { + in window: Window; + out flags: WindowFlags; + error InvalidHandle; + } + + /// Sets the `size` of `window` and returns the new actual size. + /// NOTE: This event is meant to be used from desktop APIs and will not automatically + /// notify the window of the resize event. + syscall set_window_size { + in window: Window; + in size: Size; + out actual_size: Size; + error InvalidHandle; + } + + /// Resizes a window to the new size. + syscall resize_window { + in window: Window; + in size: Size; + error InvalidHandle; + } + + /// Changes a window title. + syscall set_window_title { + in handle: Window; + in title: str; + error InvalidHandle; + } + + /// Notifies the desktop that a window wants attention from the user. + /// This could just pop the window to the front, make it blink, show a small notification, ... + syscall mark_window_urgent { + in handle: Window; + error InvalidHandle; + } + + /// Waits for an event on the given `Window`, completing as soon as + /// an event arrived. + async_call GetWindowEvent { + in window: Window; + out event: WindowEvent; + error Cancelled; + error InProgress; + error InvalidHandle; + } + + /// Create a new widget identified by `uuid` on the given `window`. + /// Position and size of the widget are undetermined at start and a call to `place_widget` should be performed on success. + syscall create_widget { + in window: Window; + in uuid: *const UUID; + out widget: Widget; + error SystemResources; + error WidgetNotFound; + error InvalidHandle; + } + + /// Moves and resizes a widget in one. + /// + /// NOTE: The position of a widget is unrestricted, but it's size + /// may be restricted by the selected widget type. + syscall place_widget { + in widget: Widget; + + /// The desired position and size of the widget. + in desired: Rectangle; + + /// The actual position and size of the widget after the operation. + out actual: Rectangle; + + error InvalidHandle; + } + + enum WidgetControlID : u32 { ... } + + /// Triggers the `control` event of the widget with the given `message` as a payload. + syscall control_widget { + in widget: Widget; + in message: WidgetControlMessage; + error SystemResources; + error InvalidHandle; + } + + enum WidgetNotifyID : u32 { ... } + + /// Puts a `widget_notify` event into the event queue of the `Window` that owns `widget`. + /// The parameters are passed as a `WidgetNotifyEvent` to the event queue. + syscall notify_owner { + in widget: Widget; + in type: WidgetNotifyID; + in params: *const [4]usize; + error SystemResources; + error InvalidHandle; + } + + /// Returns WidgetType-associated "opaque" data for this widget. + /// + /// This is meant as a convenience tool to store additional information per widget + /// like internal state and such. + /// + /// The size of this must be known and cannot be queried. + syscall get_widget_data { + in widget: Widget; + out data: [*]align(16) u8; + error InvalidHandle; + } + + /// Returns the current location and size of the provided widget. + syscall get_widget_bounds { + in widget: Widget; + out bounds: Rectangle; + error InvalidHandle; + } + + /// Creates a new desktop with the given name. + syscall create_desktop { + /// User-visible name of the desktop. + in name: str; + in descriptor: *const DesktopDescriptor; + out desktop: Desktop; + error SystemResources; + } + + /// Returns the name of the provided desktop. + syscall get_desktop_name { + in desktop: Desktop; + in name_buf: ?[]u8; + out name_len: usize; + error InvalidHandle; + } + + /// Enumerates all available desktops. + syscall enumerate_desktops { + in serverlist: ?[]Desktop; + out count: usize; + } + + /// Returns all windows for a desktop handle. + syscall enumerate_desktop_windows { + in desktop: Desktop; + in window: ?[]Window; + out count: usize; + error InvalidHandle; + } + + /// Returns desktop-associated "opaque" data for this window. + /// + /// This is meant as a convenience tool to store additional information per window + /// like position on the screen, orientation, alignment, ... + /// + /// The size of this must be known and cannot be queried. + syscall get_desktop_data { + in window: Window; + out data: [*]align(16) u8; + error InvalidHandle; + } + + /// Notifies the system that a message box was confirmed by the user. + /// + /// NOTE: This function is meant to be implemented by a desktop server. + /// Regular GUI applications should not use this function as they have no + /// access to a `MessageBoxEvent.RequestID`. + syscall notify_message_box { + /// The desktop that completed the message box. + in source: Desktop; + /// The request id that was passed in `MessageBoxEvent`. + in request_id: MessageBoxEvent.RequestID; + /// The resulting button which the user clicked. + in result: MessageBoxResult; + error BadRequestId; + error InvalidHandle; + } + + /// Posts an event into the window event queue so the window owner + /// can handle the event. + syscall post_window_event { + in window: Window; + in event: WindowEvent; + error SystemResources; + error InvalidHandle; + } + + /// Sends a notification to the provided `desktop`. + syscall send_notification { + /// Where to show the notification? + in desktop: Desktop; + /// What text is displayed in the notification? + in message: str; + /// How urgent is the notification to the user? + in severity: NotificationSeverity; + error SystemResources; + error InvalidHandle; + } + + namespace clipboard { + /// Sets the contents of the clip board. + /// Takes a mime type as well as the value in the provided format. + syscall set { + in desktop: Desktop; + in mime: str; + in value: str; + error SystemResources; + } + + /// Returns the current type present in the clipboard, if any. + syscall get_type { + in desktop: Desktop; + in type_buf: ?[]u8; + out type_len: usize; + error InvalidHandle; + } + + /// Returns the current clipboard value as the provided mime type. + /// The os provides a conversion *if possible*, otherwise returns an error. + /// The returned memory for `value` is owned by the process and must be freed with `ashet.process.memory.release`. + syscall get_value { + in desktop: Desktop; + in mime: str; + out value: []const u8; + error InvalidHandle; + error SystemResources; + error ConversionFailed; + error ClipboardEmpty; + } + } +} + +/// The service namespace implements a kernel-mediated Object Request Broker (ORB). +/// +/// It allows processes to register "Interfaces" consisting of functions that can be +/// called by other processes. +/// +/// KEY FEATURES: +/// - **Hybrid Invocation**: Interfaces can support synchronous (blocking) and/or +/// asynchronous (overlapped) invocation models. +/// - **Type Safety**: The kernel validates that the caller passes the correct number and types +/// of arguments (integers vs resources). +/// - **Resource Marshalling**: Resources passed to/from interfaces are automatically +/// bound to the receiver's process. +/// - **Context Switching**: The kernel switches the "Resource Context" of the executing thread +/// to the Interface's owning process during the execution of the handler. +namespace service { + /// An interface is a collection of synchronous functions and overlapped operations. + /// + /// Calling the functions will directly invoke an associated function in the creating + /// process. + /// + /// Calling an overlapped operation will trigger the creating process which eventually + /// completes the operation. + /// + /// Each interface is uniquely identified by a UUID which allows identification of the + /// interface and asserts the contract for the semantics of the functions and overlapped ops. + /// + /// In addition to the UUID, each interface has a signature that asserts the compatibility + /// between the producer and the consumer of the interface. + /// + /// Interfaces can have up to 256 functions and overlapped operations each, with each + /// function having up to 8 input values and a single return value, and overlapped operations + /// having up to 8 input and 8 output values. + /// + /// NOTE: As interface resources are hold both by the service and the consumer, an interface is + /// always tied to the lifetime of to the creating process. This ensures that when the process + /// is terminated, the interface resource will be destroyed. + /// + /// This is not done through the tethering interface, but through a dedicated mechanism that + /// ensures correctness. + resource Interface { } + + /// Defines the type of argument and the potential transformations the + /// kernel performs when passing the argument between caller and callee. + enum MarshalType : u2 { + /// The argument/result is unused. + /// Unused values must be set to zero or otherwise the call/return is invalid. + item unused = 0; + + /// This value is reserved and should not be used. + item reserved = 1; + + /// The value is passed unmodified by the kernel. + /// NOTE: This value should be used for integers, enumerations, + /// raw pointers and so on. + item raw = 2; + + /// The value passed is a system resource. + /// + /// MARSHALLING rules: + /// - A zero value is never marshalled and passed verbatim. This allows passing optional resources. + /// + /// - For `invoke` / `Invoke` inputs: + /// The kernel interprets resource handles in the *calling thread's current resource context*, + /// validates them, then creates an `at_least_weak` binding for the process that owns `interface`. + /// The `Function` / `AsyncHandler` receives resource handles valid in its own resource context. + /// + /// - For `invoke` output: + /// If the signature declares a resource output, the kernel interprets the returned handle in the + /// resource context active during the `Function` call (the interface handler context). + /// If it is non-zero and valid, the kernel creates a strong binding for the `invoke` caller's + /// resource context and returns the translated handle. + /// If it is non-zero and invalid, `invoke` returns `error.BadReturnValue`. + /// + /// - For `complete_request` outputs: + /// The kernel interprets resource handles in `results` in the *calling thread's current resource context*. + /// For each non-zero valid handle, it creates a strong binding for the resource context that scheduled + /// the original `Invoke` request and returns the translated handle in `Invoke.results`. + item resource = 3; + } + + /// Defines the signature of a function or overlapped operation. + /// + /// NOTE: For function signatures, `outputs[1..]` must be set to + /// `MarshalType.unused`. + /// + /// NOTE: For both `inputs` and `outputs`: As soon as an index in the + /// array is `MarshalType.unused`, all following items must also be + /// `MarshalType.unused`. + bitstruct FunctionSignature : u32 { + /// Defines the number and type of the input arguments. + /// `inputs[0]` is the first argument, `inputs[7]` is the eighth argument. + field inputs: [8]MarshalType; + + /// Defines the number and type of the result values. + /// `outputs[0]` is the first result, `outputs[7]` is the eighth result. /// - /// NOTE: A click with the mouse is valid when, and only when: - /// `mouse_button_down` and `mouse_button_up` with the left mouse button happen on the - /// same widget. The hovered widget *may* change in between the mouse down and mouse up, - /// but the click will still be recognized. - /// NOTE: A click with the keyboard is valid when, and only when: - /// `key_press` and `key_release` happen without changing the focused widget, and only when - /// the focus giving key (space, return, ...) was pressed without any other key interrupting. - item click = 3; + /// NOTE: Only `outputs[0]` may be set for functions. Overlapped operations + /// can use all eight values. + field outputs: [8]MarshalType; + } - //? keyboard input: + /// A token used by the interface to identify a pending asynchronous request. + /// + /// NOTE: Request tokens are valid globally and may be passed between processes. + enum RequestToken : u32 { ... } - /// A key was pressed on the keyboard. - item key_press = 4; + /// The signature of the asynchronous request handler function registered by an interface. + /// + /// **Parameters:** + /// 1. `context`: The opaque pointer associated with the interface. + /// 2. `request`: The token to the incoming request. + /// 3. `operation`: The index of the operation being called. + /// 4. `arguments`: Pointer to the 8 input arguments provided by the caller. + /// + /// **Behavior:** + /// The handler should store the `req` and the values inside `args` (if needed) and return immediately. + /// The operation is completed later via `complete_request`. + /// + /// NOTE: A handler function should not yield the executing thread, as this will generate + /// hard to debug scenarios. + /// + /// NOTE: `arguments` must be assumed invalid after the return of the callback. + /// + /// NOTE: The handler should perform a strong binding of resources inside `arguments` if it must + /// retain independent access even if the caller later unbinds/releases its handles. + /// This still does not prevent explicit destruction of the resource. + typedef AsyncHandler = fnptr(context: ?*anyopaque, request: RequestToken, operation: u8, arguments: *const [8]usize) void; - /// A key was released on the keyboard. - item key_release = 5; + /// The signature of the asynchronous cancellation handler function registered by an interface. + /// + /// **Parameters:** + /// 1. `context`: The opaque pointer associated with the interface. + /// 2. `request`: The token to the incoming request. + /// + /// **Behavior:** + /// This handler is invoked inside `overlapped.cancel` when the operation wasn't completed yet. + /// The implementor shall perform potential cancellation of the request and must not call `complete_request` + /// or `fail_request` anymore, as `request` will be invalidated after the cancel handler returns. + /// + /// NOTE: A handler function should not yield the executing thread, as this will generate + /// hard to debug scenarios. + typedef CancelHandler = fnptr(context: ?*anyopaque, request: RequestToken) void; - //? mouse specific extras: + /// The signature of a synchronous function call registered by an interface. + /// + /// **Parameters:** + /// 1. `context`: The opaque pointer associated with the interface. + /// 2. `arguments`: Pointer to the 8 input arguments provided by the caller. + /// + /// The function may or may not return a value depending on it's signature. + /// + /// If the signature does not define a return value, the function must return zero. + /// + /// NOTE: A synchronous function should not yield the executing thread, as this will generate + /// hard to debug scenarios. + /// + /// NOTE: `arguments` must be assumed invalid after the return of the callback. + typedef Function = fnptr(context: ?*anyopaque, arguments: *const [8]usize) usize; - /// The mouse was moved inside the rectangle of the widget. + /// Creates a new interface that can be invoked by other processes. + /// + /// NOTE: After creation, the interface is not yet discoverable with `enumerate`. + /// This way, private interfaces can be passed between processes. + /// + /// NOTE: If an interface should be available as a system service, it must be + /// published with `register`. + syscall create { + /// The unique identifier of the interface. /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item mouse_enter = 6; - - /// The mouse was moved outside the rectangle of the widget. + /// This UUID defines the contract this interface implements. /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item mouse_leave = 7; + /// NOTE: The kernel copies the UUID object internally. + in uuid: *const UUID; - /// The mouse stopped for some time over the widget. - /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item mouse_hover = 8; + /// A human-readable name for enumeration and debugging. + in name: str; - /// A mouse button was pressed over the widget. + /// Defines the signatures and count of synchronous function calls in the interface. /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item mouse_button_press = 9; + /// NOTE: The kernel will create an internal copy of this array, so userland + /// can reuse the memory freely after this call. + in sync_signatures: []const FunctionSignature; - /// A mouse button was released over the widget. + /// Defines the signatures and count of overlapped operations in the interface. /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item mouse_button_release = 10; + /// NOTE: The kernel will create an internal copy of this array, so userland + /// can reuse the memory freely after this call. + in async_signatures: []const FunctionSignature; - /// The mouse was moved over the widget. + /// The opaque context pointer ("this") passed to the functions in `vtable` and the `async_handler`. /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item mouse_motion = 11; + /// NOTE: This can be used to implement a stateful interface that allows a process to create + /// the same interface more than once and still have context which of the interfaces were + /// called. + in context: ?*anyopaque; - /// A vertical or horizontal scroll wheel was scrolled over the widget. + /// The synchronous implementation functions (vtable). /// - /// NOTE: This event can only happen when `hit_test_visible` was set - /// in the widget creation flags. - item scroll = 12; - - //? drag&drop operations: - - /// The user dragged a payload into the rectangle of this widget. + /// NOTE: Must have the same number of elements as `sync_signatures`. /// - /// NOTE: This event can only happen when `allow_drop` was set in the - /// widget creation flags. - item drag_enter = 13; + /// NOTE: The kernel will create an internal copy of this array, so userland + /// can reuse the memory freely after this call. + in vtable: []const Function; - /// The user dragged a payload out of the rectangle of this widget. + /// The asynchronous request handler. /// - /// NOTE: This event can only happen when `allow_drop` was set in the - /// widget type creation flags. - item drag_leave = 14; - - /// The user dragged a payload over the rectangle of this widget. + /// NOTE: All asynchronous requests go through the same function handler, and + /// dispatch must happen in userland. /// - /// NOTE: This event can only happen when `allow_drop` was set in the - /// widget type creation flags. - item drag_over = 15; - - /// The user dropped a payload into this widget. + /// The kernel ensures that argument marshalling will be properly performed, + /// and function will never be out of range for the interface. /// - /// NOTE: This event can only happen when `allow_drop` was set in the - /// widget type creation flags. - item drag_drop = 16; - - //? clipboard operations: + /// NOTE: May be `null` if, and only if, `async_signatures.len == 0`. + in async_handler: ?AsyncHandler; - /// The user requested a clipboard copy operation, usually by pressing 'Ctrl-C'. + /// The asynchronous cancellation handler. /// - /// NOTE: This event can only happen when `clipboard_sensitive` was set in - /// the widget type creation flags. - item clipboard_copy = 17; - - /// The user requested a clipboard paste operation, usually by pressing 'Ctrl-V'. + /// NOTE: All cancellation requests go through the same function handler, and + /// dispatch must happen in userland. /// - /// NOTE: This event can only happen when `clipboard_sensitive` was set in - /// the widget type creation flags. - item clipboard_paste = 18; - - /// The user requested a clipboard cut operation, usually by pressing 'Ctrl-X'. + /// NOTE: May be `null` even if `async_signatures.len > 0`. /// - /// NOTE: This event can only happen when `clipboard_sensitive` was set in - /// the widget type creation flags. - item clipboard_cut = 19; - - //? widget specific: - - //? TODO: Implement ResizedArgs with "desired size, actual size" - /// The widget was resized with a call to `place_widget`. + /// NOTE: If `null`, the kernel just invalidates the `RequestToken` on cancellation + /// and will not allow completion of the request. /// - /// NOTE: This event will not fire if the widget was only moved. - item resized = 21; + /// The userland process may still perform unnecessary work. + in cancel_handler: ?CancelHandler; - /// The widget should draw itself. - item paint = 20; + /// The created interface resource. + out interface: Interface; - /// User pressed the "context menu" button or did a - /// secondary mouse button click on the widget. - item context_menu_request = 22; - - /// The widget received focus via mouse or keyboard. - item focus_enter = 23; + /// A signature inside `sync_signatures` or `async_signatures` is invalid. + /// + /// This means either: + /// - A function has more than a single output + /// - A signature has `MarshalType.unused` between used inputs or outputs. + /// - `MarshalType.reserved` is used. + error InvalidSignature; - /// The widget lost focus after receiving it. - item focus_leave = 24; + /// Returned if a parameter is malformed. + /// + /// Reasons for this may be: + /// - `sync_signatures.len != vtable.len`. + /// - `uuid` is nil (all bits zero) or omni (all bits one). + /// - `async_handler` is null, but `async_signatures.len > 0`. + error InvalidValue; - ... + error SystemResources; } -} -union WindowEvent { - field event_type: Type; + /// Returns the UUID of the interface. + syscall get_interface_uuid { + in interface: Interface; + out uuid: UUID; - field mouse: MouseEvent; - field keyboard: KeyboardEvent; - field widget_notify: WidgetNotifyEvent; + /// `interface` is not a valid interface resource. + error InvalidHandle; + } - enum Type : u16 { - item widget_notify = 0; + /// Returns the name of the interface. + syscall get_interface_name { + in interface: Interface; + in name_buf: ?[]u8; - item key_press = 1; - item key_release = 2; + /// If `name_buf` is null, the total length of the name. + /// If `name_buf` is not null, the number of bytes written to `name_buf`. + out name_len: usize; - item mouse_enter = 3; - item mouse_leave = 4; - item mouse_motion = 7; - item mouse_button_press = 6; - item mouse_button_release = 5; + /// `interface` is not a valid interface resource. + error InvalidHandle; + } - /// The user requested the window to be closed. - item window_close = 8; + /// Registers an interface as a systemwide service. + /// + /// All registered interfaces can be discovered through `enumerate`. + /// + /// NOTE: Revoking the registration is not possible by design. To unpublish + /// a service, the interface resource must be destroyed. + /// + /// NOTE: Registering an `interface` twice is idempotent and does nothing. + syscall register { + /// The interface that shall be published. + in interface: Interface; - /// The window was minimized and is not visible anymore. - item window_minimize = 9; + /// `interface` is not a valid interface resource. + error InvalidHandle; - /// The window was restored from minimized state. - item window_restore = 10; + error SystemResources; + } - /// The window is currently moving on the screen. Query `window.bounds` to get the new position. - item window_moving = 11; + /// Enumerates all registered services. + syscall enumerate { + /// If not `null`, the enumeration returns only interfaces + /// with the given unique identifier. + /// If `null` will enumerate all interfaces. + in uuid: ?*const UUID; - /// The window was moved on the screen. Query `window.bounds` to get the new position. - item window_moved = 12; + /// If not `null`, the kernel will write the registered interfaces to this array. + /// + /// NOTE: The interface handles will be bound to the calling process with `BindOperation.at_least_weak` + /// to ensure resource access. + in services: ?[]Interface; - /// The window size is currently changing. Query `window.bounds` to get the new size. - item window_resizing = 13; + /// Number of elements written to `services` or total number of registered + /// interfaces. + out count: usize; - /// The window size changed. Query `window.bounds` to get the new size. - item window_resized = 14; + error SystemResources; } -} -/// Event structures shared between different event groups -union SharedEventType { - field input: InputEvent.Type; - field widget: WidgetEvent.Type; - field window: WindowEvent.Type; -} - -struct MouseEvent { - field event_type: SharedEventType; //? MUST BE FIRST! + /// Invokes an interface function synchronously. + /// + /// **Execution Flow:** + /// 1. Kernel validates arguments against signature. + /// 2. Kernel marshals input resources. + /// 3. Kernel context-switches to interface process. + /// 4. Kernel calls `vtable[func_index]`. + /// 5. Kernel context-switches back. + /// 6. Kernel optionally marshals the output resource. + syscall invoke { + in interface: Interface; + + /// Index of the function which shall be invoked. + in function: u8; + + /// The arguments passed to `function`. + /// + /// NOTE: The kernel will perform marshalling as defined in the function signature. + /// + /// NOTE: The kernel will validate that all unused arguments are zero. + /// + /// NOTE: Resource handles passed here are assumed to be valid in the callers resource context. + in arguments: [8]usize; - field x: i16; - field y: i16; - field dx: i16; - field dy: i16; - field button: MouseButton; -} + /// The return value of the function. + /// + /// NOTE: If a resource handle is returned, the resource will be strongly bound to the callers process. + /// + /// NOTE: If a resource is expected to be returned, but zero is returned, the kernel will pass the zero. + /// This allows returning optional resources. Userland has to validate that rules for non-zero only returns. + out result: usize; -struct KeyboardEvent { - field event_type: SharedEventType; //? MUST BE FIRST! + /// `interface` is not a valid interface resource. + error InvalidHandle; - /// The raw usage code for the key. Meaning depends on the layout; - /// kinda represents the physical position on the keyboard. - field usage: KeyUsageCode; + /// `function` does not exist. + error InvalidFunction; - /// If set, the pressed key combination has a mapping in the current - /// keyboard layout that produces text input. - /// - /// NOTE: This doesn't necessarily contains printable codes, but can also contain - /// combining characters like `U+0301` (Combining Acute Accent). - /// - /// NOTE: This isn't a true *composed* text input and cannot be directly used in a - /// text field or such. This is primarily meant to be passed into an input - /// method editor. - /// - /// LORE: This field isn't a perfect solution, but it's good enough for what we're trying to - /// achieve: International text input. - /// The idea of using combining characters for dead keys allows the IME to actually compose - /// a sequence of `U+0301` (Combining Acute Accent), `U+0041` (Latin Capital Letter A) to be composed - /// into `U+00C1` (Latin Capital Letter A With Acute) instead of emitting two codepoints. - /// - /// This method is flexible enough to be future proof and expansible. - /// - field text: ?str; + /// The kernel validation of the arguments failed. + /// + /// This can have two reasons: + /// - An unused argument is non-zero. + /// - A resource argument is not a valid resource handle. + error InvalidArg; + + /// Returned in the following cases: + /// - The invoked function returns a resource handle, but this + /// resource handle was neither valid nor zero. + /// - The invoked function return value is unused, but the + /// function returned a non-zero value. + /// + /// LORE: This error is sadly the best way to handle implementation bugs + /// in the interface. As the implementor process has already surrendered + /// control back to the kernel, there's no channel back to the implementor + /// to inform it about misbehaviour. + error BadReturnValue; - /// The key in this event was pressed or released - field pressed: bool; + error SystemResources; + } - /// The modifier keys currently active - field modifiers: KeyboardModifiers; -} + /// Schedules an overlapped interface operation. + /// + /// **Execution Flow:** + /// 1. Kernel validates arguments against signature. + /// 2. Kernel allocates an internal Request State. + /// 3. Kernel marshals input resources. + /// 4. Kernel context-switches to Interface process (temporarily). + /// 5. Kernel calls `async_handler`. + /// 6. Kernel context-switches back and returns the ARC to the caller. + async_call Invoke { + in interface: Interface; + + /// Index of the asynchronous operation that shall be invoked. + in operation: u8; + + /// The arguments passed to `operation`. + /// + /// NOTE: The kernel will perform marshalling as defined in the operation signature. + /// + /// NOTE: The kernel will validate that all unused arguments are zero. + /// + /// NOTE: Resource handles passed here are assumed to be valid in the callers resource context. + in arguments: [8]usize; -bitstruct KeyboardModifiers : u16 { - field shift: bool; - field alt: bool; - field ctrl: bool; - field gui: bool; - field shift_left: bool; - field shift_right: bool; - field ctrl_left: bool; - field ctrl_right: bool; - field alt_graph: bool; - field gui_left: bool; - field gui_right: bool; - reserve u5 = 0; -} + /// The results of the operation. + /// + /// NOTE: Filled by the kernel when the interface implementor calls `complete_request`. + /// + /// NOTE: Resource handles returned here are bound strongly to the schedulers resource context. + out results: [8]usize; -struct Point { - const zero: Point = .{ .x = 0, .y = 0 }; + /// `interface` is not a valid interface resource. + error InvalidHandle; - field x: i16; - field y: i16; -} + /// `operation` does not exist. + error InvalidFunction; -struct Size { - const empty: Size = .{ .width = 0, .height = 0 }; - const max: Size = .{ .width = 0xFFFF, .height = 0xFFFF }; + /// The kernel validation of the arguments failed. + /// + /// This can have two reasons: + /// - An unused argument is non-zero. + /// - A resource argument is not a valid resource handle. + error InvalidArg; - field width: u16; - field height: u16; -} + /// The operation was failed by a call to `fail_request`. + error RequestFailed; -struct Rectangle { - field x: i16; - field y: i16; - field width: u16; - field height: u16; -} + error SystemResources; + } -struct VideoMemory { - /// Pointer to the first pixel of the first scanline. + /// Completes a pending asynchronous request (called by the interface implementor). /// - /// Each scanline is `.stride` elements separated from - /// each other and contains `width` valid elements. - /// - /// There are `height` total scanlines available. - field base: [*]align(4) Color; - - /// Length of a scanline. - field stride: usize; - - /// Number of valid elements in a scanline - field width: u16; + /// NOTE: This consumes the `request` token and wakes the caller (completing their ARC). + syscall complete_request { + /// The request token passed to `AsyncHandler`. + in request: RequestToken; - /// Number of valid scanlines. - field height: u16; -} - -struct FileSystemInfo { - /// system-unique id of this file system - field id: FileSystemId; - /// binary infos about the file system - field flags: Flags; - /// user addressable file system identifier ('USB0', ...) - field name: [8]u8; //? TODO: Use max_fs_name_len instead of 8 + /// The return values/resources. + /// + /// NOTE: Resources in this array will be marshalled to the caller as specified + /// in the operation signature. + /// + /// NOTE: Resource handles passed here are assumed to be valid in the calling thread's current resource context. + in results: [8]usize; - /// string identifier of a file system driver ('FAT32', ...) - field filesystem: [32]u8; //? TODO: USe max_fs_type_len instead of 32 + /// `request` is not a valid pending request. + error InvalidHandle; - bitstruct Flags : u16 { - /// is the system boot disk - field system: bool; + /// The kernel validation of the results failed. + /// + /// This can have two reasons: + /// - An unused result is non-zero. + /// - A resource result is not a valid resource handle. + error InvalidArg; - /// the file system can be removed by the user - field removable: bool; + error SystemResources; + } - /// the file system is mounted as read-only - field read_only: bool; + /// Rejects/fails a pending asynchronous request (called by the interface implementor). + /// + /// This completes the caller's ARC with a generic `RequestFailed` error. + /// + /// NOTE: This consumes the `request` token and wakes the caller (completing their ARC). + syscall fail_request { + /// The request token passed to `AsyncHandler`. + in request: RequestToken; - reserve u13 = 0; + /// `request` is not a valid pending request. + error InvalidHandle; } } -struct FileInfo { - /// The name of the file. - field name: [120]u8; //? TODO: Use max_file_name_len instead of hardcoded array size - /// The size of the file in bytes. - field size: u64; - field attributes: FileAttributes; - field creation_date: DateTime; - field modified_date: DateTime; -} - - +//? TODO: Review this namespace. /// /// The I/O namespace contains APIs to interface with external hardware like serial ports, I²C busses and so on. /// @@ -3250,35 +6591,34 @@ namespace io { ... } - //? syscall enumerate { - //? in list: []SerialPortID; - - //? out count: usize; - //? } + syscall enumerate { + in list: ?[]SerialPortID; + out count: usize; + } - //? /// Queries information about the given serial port id. - //? syscall query_metadata { - //? in id: SerialPortID; - //? in name_buf: ?[]u8; + /// Queries information about the given serial port id. + syscall query_metadata { + in id: SerialPortID; + in name_buf: ?[]u8; - //? out name_len: usize; + out name_len: usize; - //? /// The given id does not exist. - //? error NotFound; - //? } + /// The given id does not exist. + error NotFound; + } resource SerialPort { } - //? syscall open { - //? in id: SerialPortID; - //? out port: SerialPort; + syscall open { + in id: SerialPortID; + out port: SerialPort; - //? /// The given id does not exist. - //? error NotFound; + /// The given id does not exist. + error NotFound; - //? /// The resource is already opened. - //? error ResourceBusy; - //? } + /// The resource is already opened. + error ResourceBusy; + } /// /// Changes the configuration of a serial port and returns the new configuration. @@ -3288,25 +6628,25 @@ namespace io { async_call configure { in port: SerialPort; - //? TODO: in baud_rate: ?u32; - //? TODO: in word_size: ?u8; - //? TODO: in stop_bits: ?StopBits; - //? TODO: in parity: ?Parity; - //? TODO: in control_flow: ?ControlFlow; + in baud_rate: ?u32; + in word_size: ?u8; + in stop_bits: ?StopBits; + in parity: ?Parity; + in control_flow: ?ControlFlow; - //? TODO: /// Selects which software control flow words control the transmitter - //? TODO: /// activity. - //? TODO: /// - //? TODO: /// NOTE: This is usually the same as `sw_control_flow_tx`. - //? TODO: in sw_control_flow_rx: ?SoftwareControlFlow; + /// Selects which software control flow words control the transmitter + /// activity. + /// + /// NOTE: This is usually the same as `sw_control_flow_tx`. + in sw_control_flow_rx: ?SoftwareControlFlow; - //? TODO: /// Selects which software control flow words are transmitted when - //? TODO: /// the own receive buffer is full. - //? TODO: /// - //? TODO: /// NOTE: This is usually the same as `sw_control_flow_rx`. - //? TODO: in sw_control_flow_tx: ?SoftwareControlFlow; + /// Selects which software control flow words are transmitted when + /// the own receive buffer is full. + /// + /// NOTE: This is usually the same as `sw_control_flow_rx`. + in sw_control_flow_tx: ?SoftwareControlFlow; - in acceptable_baud_error: f32; //? + in acceptable_baud_error: f32; out current_baud_rate: u32; out current_data_bits: u8; @@ -3338,13 +6678,13 @@ namespace io { async_call control { in port: SerialPort; - //? /// The new state that should be applied for DTR (Data Terminal Ready). - //? TODO: in dtr: ?bool; + /// The new state that should be applied for DTR (Data Terminal Ready). + in dtr: ?bool; /// The new state that should be applied for RTS (Request To Send). /// - //? /// NOTE: This is also called `RTR` when used for modern hardware control flow. - //? TODO: in rts: ?bool; + /// NOTE: This is also called `RTR` when used for modern hardware control flow. + in rts: ?bool; error InvalidHandle; @@ -3386,7 +6726,7 @@ namespace io { } /// Writes data to the serial port. - async_call write { + async_call Write { in port: SerialPort; in data: bytestr; @@ -3400,7 +6740,7 @@ namespace io { } /// Reads data from a serial port. - async_call read { + async_call Read { in port: SerialPort; in data: bytebuf; @@ -3426,10 +6766,10 @@ namespace io { /// /// In DMX, this is used to signal the start of a new frame. /// - async_call break { + async_call Break { in port: SerialPort; - in duration: Duration; + in duration: clock.Duration; error InvalidHandle; @@ -3437,11 +6777,11 @@ namespace io { error Unsupported; } - enum SerialPortError : u8 { item none = 0; item break_detected = 1; item parity_error = 2; + item framing_error = 3; } enum StopBits : u8 { @@ -3454,10 +6794,10 @@ namespace io { /// No parity will be used. item none = 0; - /// The parity bit will contain a `1` if the sum of all data bits are even. + /// The parity bit will contain a `0` if the sum of all data bits are even. item even = 1; - /// The parity bit will contain a `1` if the sum of all data bits are odd. + /// The parity bit will contain a `0` if the sum of all data bits are odd. item odd = 2; /// The parity bit will always contain a `1`. @@ -3551,7 +6891,7 @@ namespace io { out count: usize; } - /// Queries information about the given serial port id. + /// Queries information about the given I²C bus id. syscall query_metadata { in id: BusID; in name_buf: ?[]u8; @@ -3605,7 +6945,7 @@ namespace io { in bus: Bus; /// A mutable sequence of I²C operations. Will be processed first-to last and - /// the pointed `Operation`s will be changed during + /// the pointed `Operation`s will be changed during execution to report results. in sequence: []Operation; /// The number of successfully processed elements from `sequence`. @@ -3697,4 +7037,126 @@ namespace io { } } } -} \ No newline at end of file +} + +//? +//? Global Types +//? + + +struct Point { + const zero: Point = .{ .x = 0, .y = 0 }; + + field x: i16; + field y: i16; +} + +struct Size { + const empty: Size = .{ .width = 0, .height = 0 }; + const max: Size = .{ .width = 0xFFFF, .height = 0xFFFF }; + + field width: u16; + field height: u16; +} + +struct Rectangle { + field x: i16; + field y: i16; + field width: u16; + field height: u16; +} + +/// +/// An 8-bit color value with a specialized encoding suitable for embedding +/// a practical set of 256 colors. +/// +/// The color encoding is basically a HSV (hue, saturation, value) color with 8 bits, using +/// 3 bits for the hue, 3 bits for the value and 2 bits for the saturation. +/// +/// Naively mapping out the values to the HSV values has two problems though: +/// 1. A value of 0 maps all colors to black, meaning that we would have 64 different +/// types of blacks, which all would encode have the rgb value `(0, 0, 0)`. +/// 2. A saturation of 0 maps all colors to gray, effectively ignoring the hue. +/// This creates the situation that in addition to having 64 blacks, we would also +/// have each gray tone 8 times, wasting even more encoding space. +/// +/// To address these two problems, the color scheme uses a modified mapping: +/// +/// - `hue` is used without special interpretation. +/// - `value` maps to a range of `[1:8]` instead of `[0:7]`, allowing 8 different +/// values that are all not black. +/// - `saturation` is used without special interpretation except for zero: +/// If the `saturation` field is zero, `hue` and `value` are interpreted together as a 6 bit +/// integer storing the brightness of gray. +/// +/// This yields a color space which has the following properties: +/// +/// - 64 true gray levels ranging from black to white. +/// - 8 different hues (red, yellow, lime, green, cyan, blue, purple, magenta). +/// - 3 different levels of saturation for each non-gray color. +/// - black maps to `0x00` (but white does not map to `0xFF`). +/// +/// This means we have all 256 colors mapped to a distinct, meaningful color that still allows +/// programmatic conversion from and to the color without the need of a look-up table that +/// would require searching the correct color. +/// +/// NOTE: This color encoding shall be referred to as "Ashet HSV". +/// +/// LORE: This color encoding was developed over the course of several days, playing around with +/// many different encodings. +/// The color encodings/palettes were tested on a diverse set of images, including game screenshots, +/// photographs, artificial images, vector graphics and so on. +/// +/// The "Ashet HSV" encoding showed the best visual matches for most pictures, allowing both visual +/// fidelity on the color side, but also allowing both bright and dark images to work really well. +/// +bitstruct Color : u8 { + const black: Color = .{ .hue = 0, .value = 0, .saturation = 0 }; + const white: Color = .{ .hue = 7, .value = 7, .saturation = 0 }; + const red: Color = .{ .hue = 0, .value = 7, .saturation = 3 }; + const yellow: Color = .{ .hue = 1, .value = 7, .saturation = 3 }; + const lime: Color = .{ .hue = 2, .value = 7, .saturation = 3 }; + const green: Color = .{ .hue = 3, .value = 7, .saturation = 3 }; + const cyan: Color = .{ .hue = 4, .value = 7, .saturation = 3 }; + const blue: Color = .{ .hue = 5, .value = 7, .saturation = 3 }; + const purple: Color = .{ .hue = 6, .value = 7, .saturation = 3 }; + const magenta: Color = .{ .hue = 7, .value = 7, .saturation = 3 }; + + /// The hue of the color, encoded as 0 = 0° (red), 7 = 315° (magenta). + field hue: u3; + + /// The value of the color, with 0 = 12.5% brightness and 7 = 100% brightness. + field value: u3; + + /// The saturation of the color, encoded as 0 = desaturated, and 3 = fully saturated. + /// + /// NOTE: The value is encoded as the uppermost 2 bits, so a check if saturation is 0 can be + /// performed by doing a less-than operation interpreting the color as an integer. + field saturation: u2; + + struct RGB888 { + field r: u8; + field g: u8; + field b: u8; + } + + /// 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian + /// + /// Layed out as a `u32` encoding `0xAARRGGBB`. + enum ARGB8888 : u32 { ... } + + /// 32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian + /// + /// Layed out as a `u32` encoding `0xAABBGGRR`. + enum ABGR8888 : u32 { ... } +} + +//? +//? TODO: Move these types into the proper namespaces or decide they +//? are actually top-level. +//? + + +struct UUID { + field bytes: [16]u8; +} diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi new file mode 100644 index 00000000..f246dd72 --- /dev/null +++ b/src/abi/src/network.abi @@ -0,0 +1,2279 @@ + +/// An address of the IPv4 internet protocol. +struct IPv4 { + /// The four bytes of the IP address. + /// + /// NOTE: This is a u32 that is always encoded in network byte order. + field addr: [4]u8 ; //? TODO: align(4) +} + +/// An address of the IPv6 internet protocol. +struct IPv6 { + /// The 16 bytes of the IP address. + /// + /// NOTE: This is always encoded in network byte order. + field addr: [16]u8; //? TODO: align(4) + + /// The interface for which this IP address is valid. + /// + /// NOTE: For non-link-local addresses, scope must be `link.InterfaceId.any`. + field scope: link.InterfaceId; +} + +/// A polymorphic IP address that can be both IPv4 or IPv6. +struct IP { + /// Defines which field of `addr` is active. + /// + /// NOTE: Must be `Type.ipv4` or `Type.ipv6`. + field type: Type; + + /// Union of the possible address types. + field addr: AnyAddr; + + enum Type : u8 { + item ipv4 = 0; + item ipv6 = 1; + + /// Not a concrete type of IP type, but a sentinel different + /// kernel APIs use for defining that they don't scope a syscall or + /// a operation to a specific IP type. + item any = 255; + } + + union AnyAddr { + /// Active when `IP.type == Type.ipv4`. + field v4: IPv4; + + /// Active when `IP.type == Type.ipv6`. + field v6: IPv6; + } +} + +/// An endpoint defines a connection target for TCP and UDP connections. +/// It is a tuple formed of an IP address and a port. +struct EndPoint { + /// IP address of the connection endpoint. + field ip: IP; + + /// The port number of the connection endpoint. + /// + /// NOTE: Uses host byte order. + field port: u16; +} + +/// +/// TODO: Write about the general idea of network interfaces. +/// +/// TODO: Talk about kernel subsystems being enabled/disabled and what each subsystem +/// is supposed to to. +/// +/// Kernel Subsystems +/// ================= +/// +/// IPv4: The kernel's built-in IPv4 stack. +/// This subsystem implements a regular IPv4 stack. If disabled, this interface +/// won't allow IPv4 operation through kernel interfaces anymore. +/// +/// NOTE: Disabling the IPv4 stack for an interface will remove all IPv4 addresses +/// and routes for this interface. +/// IPv6: +/// This subsystem implements a regular IPv6 stack. If disabled, this interface +/// won't allow IPv6 operation through kernel interfaces anymore. +/// +/// NOTE: Disabling the IPv6 stack for an interface will remove all IPv6 addresses +/// and routes for this interface. +/// +/// DHCPv4: +/// TODO: Write subsystem docs +/// This subsystem automatically performs DHCP management for the interface. +/// If enabled, the kernel automatically requests and refreshes DHCP leases, +/// and manages the routes. +/// +/// NOTE: This subsystem is disabled by default. +/// +/// DHCPv6: +/// TODO: Write subsystem docs +/// +/// NOTE: This subsystem is disabled by default. +/// +/// SLAAC: +/// TODO: Write subsystem docs +/// +/// NOTE: This subsystem is enabled by default. +/// +/// +/// TODO: Write how routing works in Ashet OS. +/// +namespace link { + /// Unique identifier of a network interface. + /// + /// NOTE: Interface ids are allocated in a monotonically increasing + /// way, and will be stable until a network interface is removed + /// from the kernel. + /// + /// NOTE: Id allocation will never allocate any of the named values inside this + /// enumeration. + /// + /// NOTE: Interface ids received from any kernel syscall or overlapped operation + /// are ephemeral and are only guaranteed to be valid until the next yielding + /// syscall. + /// + /// NOTE: Except `loopback`, the enumeration order of interfaces is unspecified + /// and the ids cannot be assumed stable between reboots. + enum InterfaceId : u32 { + /// The loopback interface is a virtual interface + /// that makes all sent packets be received by the same + /// interface again. + /// + /// This way, sockets can be bound to a local interface + /// and communicate without affecting any external systems. + /// + /// NOTE: The loopback interface has the IP addresses `127.0.0.1/8` + /// and `::1/128` by default. + /// + /// The kernel also adds a default connected route for these + /// two addresses. + item loopback = 0; + + /// A sentinel value that can be used to annotate the absence of a + /// specific interface. + /// + /// NOTE: This is not a true interface that can be used to query + /// information, but is a required "workaround" for IPv6 + /// scopes to be able to encode that an IP address isn't + /// scoped to a specific interface. + /// + /// NOTE: Using this on all APIs that don't explicitly mention it yields + /// the error `InvalidInterface`. + item any = 0xFFFFFFFF; + + ... + } + + /// Enumeration of all supported network interface types. + enum InterfaceType : u8 { + /// The interface is supported by the kernel, but the type + /// of network interface does not fit any of the other categories. + item unknown = 0; + + /// The interface is a loopback interface. + item loopback = 1; + + /// The interface is an Ethernet (IEEE 802.3) interface. + item ethernet = 2; + + /// The interface is virtual and is controlled by software. + item virtual = 3; + + /// The interface is a WLAN (IEEE 802.11) interface. + item wifi = 4; + + /// The interface is based on IEEE 802.15.4 (e.g. 6LoWPAN/Thread/Zigbee-style links). + item ieee_802_15_4 = 5; + + /// The interface is Bluetooth-based (e.g. PAN/BNEP or IPv6-over-BLE/IPSP). + item bluetooth = 6; + + /// The interface is point-to-point (e.g. PPP/SLIP). Usually has no meaningful link-layer address. + item point_to_point = 7; + + /// The interface is InfiniBand-based (IPoIB). + item infiniband = 8; + + /// The interface is a Wireless Body Area Network (WBAN), typically IEEE 802.15.6. + /// + /// NOTE: The underlying PHY may be narrowband, UWB, or body-coupled (HBC), + /// depending on the device. + item wban = 9; + } + + /// The physical address of a network interface. + /// + /// NOTE: This isn't just a MAC address, but it can hold several + /// different types of address. + struct PhysicalAddress { + /// Length of the physical address in bytes. + field len: u8; + + /// The type of the physical address. Specifies how `bytes` are interpreted. + field type: Type; + + /// Describes how the address value was assigned/generated (if known). + /// + /// NOTE: This is intentionally generic. For example, Bluetooth LE privacy + /// addresses (static/resolvable/non-resolvable) can be mapped onto + /// the `random_*` variants here. + field assignment: Assignment; + + /// Reserved. Must be zero. + field _reserved0: u8 = 0; + + /// Contains the bytes of the physical address. + /// + /// NOTE: The first `len` bytes are valid. All bytes beyond the + /// first `len` bytes must be zero. + /// + /// NOTE: These bytes must be interpreted according to `type`. + field bytes: [20]u8; + + /// Describes how a physical address was assigned/generated. + /// + /// NOTE: This is determined by the kernel in a best-effort fashion, and + /// may be `unknown` even if the address format is known. + enum Assignment: u8 { + /// The kernel does not know how this address was assigned. + item unknown = 0; + + /// Universally administered / externally assigned identifier. + /// + /// NOTE: For EUI-48/EUI-64 this typically means IEEE-assigned (U/L bit = 0). + item universal = 1; + + /// Locally administered identifier (not globally assigned). + /// + /// NOTE: For EUI-48/EUI-64 this typically means U/L bit = 1. + item local = 2; + + /// Randomly generated but expected to remain stable for long periods + /// (until reconfigured/reset). + /// + /// NOTE: Bluetooth LE "Static Random Address" maps here. + item random_stable = 3; + + /// Randomly generated and expected to rotate over time for privacy. + /// + /// NOTE: Wi-Fi MAC randomization and Bluetooth LE "Non-Resolvable Private Address" map here. + item random_rotating = 4; + + /// Randomly generated and expected to rotate, but can be mapped back to a stable + /// identity by peers that possess a shared secret / resolver. + /// + /// NOTE: Bluetooth LE "Resolvable Private Address" maps here. + item random_rotating_resolvable = 5; + } + + /// Enumeration of possible types for physical link addresses. + enum Type: u8 { + /// Special marker that encodes `?PhysicalAddress == null`. + /// + /// NOTE: `absent` means the `PhysicalAddress` exists in a logical sense, but is empty. + /// `null` means the `PhysicalAddress` value itself is absent/missing. + /// + /// NOTE: All addresses of this type are empty (zero bytes long). + /// + /// NOTE: A `PhysicalAddress` `null` value must be fully zeroed out: + /// - `len = 0` + /// - `assignment = Assignment.unknown` + /// - `_reserved0 = 0` + /// - `bytes = {0} ** 20`. + item null = 0; + + /// This type marks an absent physical address. + /// + /// This typically means the associated interface does not support physical addresses at all. + /// + /// NOTE: All addresses of this type are empty (zero bytes long). + /// + /// NOTE: This is a special case to handle links that have no address, + /// but without introducing "out of band" communication for absent + /// physical addresses. + /// + /// NOTE: This is distinct from `null` in that a `PhysicalAddress` exists but is empty, + /// while `null` means the `PhysicalAddress` value does not exist. + /// + /// NOTE: For `absent`, the struct must satisfy: + /// - `len = 0` + /// - `assignment = Assignment.unknown` + /// - `_reserved0 = 0` + /// - `bytes = {0} ** 20`. + item absent = 1; + + /// A physical address is available, but the kernel cannot represent the type of address. + /// + /// NOTE: Any `len` may be valid for this kind of physical address. + item unknown = 2; + + /// An address from the EUI-48 namespace. This is typically known as a MAC address. + /// + /// NOTE: These addresses are typically used with Ethernet or WLAN interfaces. + /// + /// NOTE: `PhysicalAddress.len` must be `6`. + /// + /// NOTE: See RFC 9542 for more information on this address type. + item eui_48 = 3; + + /// An address from the EUI-64 namespace. This is typically known as a MAC address. + /// + /// NOTE: These addresses are typically used with 802.15.4 based protocols (e.g. ZigBee). + /// + /// NOTE: `PhysicalAddress.len` must be `8`. + /// + /// NOTE: See RFC 9542 for more information on this address type. + item eui_64 = 4; + + /// An IPoIB (IP over InfiniBand) link-layer address. + /// + /// NOTE: This is the 20-byte "link-layer address" used by IPoIB for IPv4/ARP + /// and for IPv6 Neighbor Discovery source/target link-layer address options. + /// + /// Layout (network byte order): + /// - byte 0: Reserved flags (must be zero on send; ignore on receive) + /// - bytes 1-3: Queue Pair Number (QPN, 24-bit) + /// - bytes 4-19: Port GID (16 bytes) + /// + /// NOTE: This address is not guaranteed to be stable across reboots or even + /// network interface resets because the QPN may change. + /// + /// NOTE: In IPv6 Neighbor Discovery, the on-wire option is padded to 24 bytes + /// total; the extra padding is not part of this 20-byte address. + /// + /// See RFC 4391, Section 9.1.1 and Section 9.3. + /// + /// NOTE: `PhysicalAddress.len` must be `20`. + item infiniband = 5; + + /// A local WBAN link-layer identifier. + /// + /// NOTE: `PhysicalAddress.len` must be `2`. + /// + /// Interpretation: + /// - byte 0: WBAN / BAN identifier (local scope) + /// - byte 1: Node identifier within that WBAN + /// + /// NOTE: This is not a standalone IEEE-defined 16-bit address format. + /// It is a packing of the IEEE 802.15.6 WBAN_ID + Node_ID fields. + /// + /// NOTE: This address is *not* globally unique. It is only meaningful within + /// the given interface's WBAN. + /// + /// NOTE: This is intended to cover IEEE 802.15.6 style WBAN links, including + /// Human Body Communication (HBC) PHY variants. + item wban_local = 6; + + /// IEEE 802.15.4 short address (16-bit). + /// + /// NOTE: `PhysicalAddress.len` must be `2`. + /// + /// NOTE: The value logically encodes a `u16` in big endian format. + item ieee_802_15_4_short = 7; + } + } + + /// A bit set of several subsystems that the kernel + /// can provide per interface. + bitstruct SubsystemSet : u32 { + field ipv4: bool; + field ipv6: bool; + field dhcp4: bool; + field dhcp6: bool; + field slaac: bool; + + reserve u27 = 0; + } + + struct InterfaceDescription { + /// The type of this network interface. + field type: InterfaceType; + + /// The physical address this interface has. + field address: PhysicalAddress; + + /// The display name of the network interface. + /// + /// NOTE: The lifetime of this string is bound to the lifetime + /// of the network interface. Assume it is only valid between + /// obtaining the interface description from the kernel and the + /// next thread yield. + field name: str; + + /// Name of the NIC vendor or an empty string if unknown. + /// + /// NOTE: The lifetime of this string is bound to the lifetime + /// of the network interface. Assume it is only valid between + /// obtaining the interface description from the kernel and the + /// next thread yield. + field vendor: str; + + /// The maximum bandwidth this interface can theoretically achieve + /// in bits per second. + /// + /// NOTE: For the loopback interface, this value is always zero. + /// + /// NOTE: For virtual interfaces, the driver shall set this value + /// to either a real value or zero, if no upper bound is known. + field max_bandwidth: u64; + + /// The current bandwidth this interface has negotiated with the + /// connected network in bits per second. + /// + /// NOTE: If zero, the value cannot be determined for one of several reasons: + /// - The link is down. + /// - The driver has no way to query the current bandwidth. + /// - There is no physically realistic value (e.g. for virtual or loopback interfaces). + field current_bandwidth: u64; + + /// The set of currently enabled kernel subsystems. + field enabled_subsystems: SubsystemSet; + } + + /// Enumerates the currently available network interfaces. + syscall enumerate_interfaces { + /// Buffer that shall receive the list of interfaces or `null` if the + /// total amount of interfaces should be queried. + in list: ?[]InterfaceId; + + /// If `list` is not `null`, returns the number of elements written to `list`, + /// otherwise it returns the total number of currently available interfaces. + out count: usize; + } + + /// Queries the description of an interface. + syscall get_description { + in interface: InterfaceId; + + out description: InterfaceDescription; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + /// Queries the physical address for the interface. + syscall get_physical_address { + in interface: InterfaceId; + + out address: PhysicalAddress; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + /// Attempts to change the physical address for an interface. + async_call SetPhysicalAddress { + in interface: InterfaceId; + + in address: PhysicalAddress; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// The interface does not allow changing its physical address. + error Unsupported; + + error SystemResources; + } + + /// Enables or disables kernel subsystems for an interface. + /// + /// NOTE: The two sets `enable` and `disable` must be disjoint and + /// must not contain overlapping subsystems. + syscall control_subsystems { + /// The interface for which kernel subsystems should be enabled + /// or disabled. + in interface: InterfaceId; + + /// Every subsystem in this set will be started. + in enable: SubsystemSet; + + /// Every subsystem in this set will be stopped. + in disable: SubsystemSet; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// `enable` and `disable` are overlapping sets and conflict + /// in their semantics. + error InvalidValue; + } + + /// Queries the currently enabled subsystems for the given + /// interface. + syscall get_subsystems { + in interface: InterfaceId; + + /// The set of all enabled network subsystems for `interface`. + out enabled: SubsystemSet; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + //? IP Addressing + + /// Enumeration of potential sources for IP addresses. + enum AddressOrigin : u8 { + /// The IP address was manually added with `AddAddress`. + item manual = 0; + + /// The IP address was assigned by the DHCPv4 network subsystem. + item dhcp4 = 1; + + /// The IP address was assigned by the DHCPv6 network subsystem. + item dhcp6 = 2; + + /// The IP address was assigned by the SLAAC network subsystem. + item slaac = 3; + + /// The IP address was assigned automatically by the kernel through + /// configuration files. + item autoconfig = 4; + } + + /// A binding of an IP address to a network interface. + struct AddressBinding { + /// The IP address that is bound. + field address: IP; + + /// The prefix of the address. Defines which prefix is + /// reachable through the interface directly without routing. + /// + /// NOTE: Must be ≤ 32 for IPv4. + /// + /// NOTE: must be ≤ 128 for IPv6. + field prefix_len: u8; + + /// The origin of the IP address. + /// + /// NOTE: `AddAddress` will ignore this field and always + /// use `AddressOrigin.manual`. + field origin: AddressOrigin; + + /// The lifetime of the IP address. + /// + /// After this timestamp is reached by `clock.monotonic`, the IP address + /// will be automatically removed by the kernel. + /// + /// NOTE: The kernel will also automatically remove the associated connected route. + /// + /// NOTE: If an IP address should not expire, pass `clock.Absolute.infinity`. + field valid_until: clock.Absolute; + } + + /// Enumerates the currently available IP addresses for an interface. + syscall enumerate_addresses { + /// The interface to query. + in interface: InterfaceId; + + /// Buffer that shall receive the list of address bindings or `null` if the + /// total amount of bindings should be queried. + in bindings: ?[]AddressBinding; + + /// If `bindings` is not `null`, returns the number of elements written to `bindings`, + /// otherwise it returns the total number of currently available bindings. + out count: usize; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + /// Adds a new IP address to an interface. + /// + /// NOTE: This operation will implicitly add a new route to the routing + /// table based off (`interface`, `binding.address`, `binding.prefix_len`). + /// + /// The route will use the prefix derived from `binding.address` and will + /// set the `IPv6.scope` for the `Route.network` to `InterfaceId.any`. + /// + /// This route will have `RouteOrigin.connected`. + /// + /// NOTE: This operation is upserting on `binding.address` and will + /// replace the properties of the current address with those from + /// `binding`. + /// This will also update the connected route in the routing table. + /// + /// NOTE: This is an asynchronous call as adding IP addresses may require + /// communication with the NIC to set up filters. This could take + /// some time and thus, the operation was made overlapped. + + async_call AddAddress { + //? TODO: Add a "force: bool" parameter and a new "error NetworkConflict" (ARP/DAD conflict) + //? Detail: IPv6 requires DAD (Neighbor Solicitation for the derived IP) before the address + //? is fully usable. If DAD fails (someone else has the IP), the address must be marked + //? as duplicated and not used. + + + /// The interface which should receive a new IP binding. + in interface: InterfaceId; + + /// The binding specification for the IP address. + in binding: AddressBinding; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// Binding is not a valid value. + /// + /// This could be due to the following reasons: + /// - `binding.address` is not valid. + /// - `binding.prefix_len` cannot be applied to `binding.address`. + /// - `binding.valid_until` is in the past. + /// - `binding.address.addr.ipv6.scope` is not `interface` for link-local IPv6 addresses. + /// - `binding.address.addr.ipv6.scope` is not `InterfaceId.any` for non-link-local IPv6 addresses. + error InvalidValue; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + /// The IPv4 or IPv6 network subsystem is disabled and the address type can't be used on this interface. + error SubsystemDisabled; + + error SystemResources; + } + + /// Removes an address from the interface. + /// + /// NOTE: This operation will implicitly remove the connected route from the routing + /// table with the key (`interface`, prefix address derived from `address`, `RouteOrigin.connected`). + /// + /// NOTE: This is an asynchronous call as adding IP addresses may require + /// communication with the NIC to set up filters. This could take + /// some time and thus, the operation was made overlapped. + /// + /// NOTE: If `address` does not exist on `interface`, the operation does nothing. + /// + /// LORE: This operation has no subsystem failure possible as removing an address + /// is idempotent for non-existing addresses, thus there's no reason to + /// care for this error. The final outcome is the same: The address doesn't + /// exist on the interface. + async_call RemoveAddress { + /// The interface which should have an address removed. + in interface: InterfaceId; + + /// The address that shall be removed. + /// + /// NOTE: As for each address, only a single binding can exist, we just need + /// the address to remove the IP binding. + /// + /// NOTE: If this is an IPv6 address, `scope` must match the rules for valid IPv6 + /// addressed and `scope` must be either `interface` or `InterfaceId.any`. + in address: IP; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// `address` is not valid. + error InvalidValue; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + error SystemResources; + } + + //? Routing + + /// Enumeration of potential origins for routes. + enum RouteOrigin : u8 { + /// The route was manually created with `add_route`. + item manual = 0; + + /// The route was created by the DHCPv4 network subsystem. + item dhcp4 = 1; + + /// The route was created by the DHCPv6 network subsystem. + item dhcp6 = 2; + + /// The route was created by the SLAAC network subsystem. + item slaac = 3; + + /// The route was created by the kernel through configuration files. + item autoconfig = 4; + + /// The route was created by the kernel when adding an IP address + /// to a network interface. + item connected = 5; + } + + /// A route describes rules on where to send IP packets. + /// + /// A route is valid if: + /// - `network.type` is `gateway.type`. + /// - `prefix_len` is valid for `network.type`. + /// - `valid_until` is in the future. + /// - If the route is an IPv6 route: + /// - `network.scope` is `InterfaceId.any`. + /// - `gateway.scope` is `interface` for all link-local addresses. + /// - `gateway.scope` is `InterfaceId.any` for all other addresses. + struct Route { + /// Defines the target network for this route. + /// + /// NOTE: This value is only semantically valuable together with `prefix_len`. + /// + /// NOTE: For IPv6, `scope` must be set to `InterfaceId.any` to prevent conflicting + /// definitions with `interface`. + field network: IP; + + /// Defines how many bits of the IP in `network` identify the target network. + /// + /// NOTE: Must be ≤ 32 for IPv4. + /// + /// NOTE: must be ≤ 128 for IPv6. + field prefix_len: u8; + + /// Defines where the next-hop for the target network is and + /// sends the packets this way. + /// + /// NOTE: If set to the unspecified IP (`0.0.0.0` or `::`), the + /// route defines an on-link route and target addresses should + /// be discovered via neighbor discovery (ARP for IPv4, NDP for IPv6). + /// + /// NOTE: If not set to an unspecified IP, the address must be a valid + /// unicast address. + /// + /// NOTE: If gateway is a link-local IPv6 address, the `scope` must be the + /// `interface` of the route. + /// + /// NOTE: `gateway.type` must match `network.type`. + field gateway: IP; + + /// The interface that will be used for sending the packets to the target network. + field interface: InterfaceId; + + /// The priority is a tie-breaker for when multiple rules would match + /// with the same prefix, but different interfaces. + /// + /// Higher priorities win. + /// + /// NOTE: When two routes with the same prefix and priority match, + /// the first inserted route is taken. + field priority: u16; + + /// The source system that added this route to the routing table. + /// + /// NOTE: `add_route` will ignore the value and will always add a route + /// with `RouteOrigin.manual`. + field origin: RouteOrigin; + + /// The lifetime of the route. + /// + /// After this timestamp is reached by `clock.monotonic`, the route + /// will be automatically removed by the kernel. + /// + /// NOTE: If a route should not expire, pass `clock.Absolute.infinity`. + field valid_until: clock.Absolute; + } + + /// Enumerates the routing table. + /// + /// NOTE: The table is returned ordered longest to shortest prefix, + /// highest-to-lowest priority, then retains insertion order. + /// + /// LORE: Enumerating routes ordered is cheap for the kernel as it + /// has to keep the table ordered in-memory anyways for efficient + /// evaluation, and thus we can expose this property into userland. + syscall enumerate_routes { + /// Buffer that shall receive the list of routes or `null` if the + /// total amount of routes should be queried. + in routes: ?[]Route; + + /// If `routes` is not `null`, returns the number of elements written to `routes`, + /// otherwise it returns the total number of routes. + out count: usize; + } + + /// Adds a new route to the routing table. + syscall add_route { + /// The route to be added. + in route: Route; + + /// The route is not valid. + /// + /// See `Route` documentation for validation rules. + error InvalidValue; + + /// Another route for the same target network exists on the same interface. + /// + /// This means that another route with (`route.interface`, `route.network`, `route.prefix_len`, `route.gateway`) exists. + error Conflict; + + /// `route.interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The IPv4 or IPv6 network subsystem is disabled and the `route.network` type can't be used on `route.interface`. + error SubsystemDisabled; + + error SystemResources; + } + + /// Removes an existing route from the routing table. + /// + /// NOTE: Removing a non-existent route is idempotent and does nothing. + /// + /// LORE: This syscall has no subsystem failure possible as removing a route + /// is idempotent for non-existing routes, thus there's no reason to + /// care for this error. The final outcome is the same: The route doesn't + /// exist on the interface anymore. + syscall remove_route { + /// The route to delete. + /// + /// NOTE: When selecting which rules should be deleted, `route.priority`, + /// `route.origin`, `route.valid_until` are ignored. + in route: Route; + + /// The route is not valid. + /// + /// See `Route` documentation for validation rules. + error InvalidValue; + + /// `route.interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + //? Link state + + enum LinkState : u8 { + /// The network interface has not recognized any connection. + item down = 0; + + /// The network interface is connected to the network. + item up = 1; + } + + /// Queries the current link state of a network interface. + syscall get_link_state { + in interface: InterfaceId; + + /// Current state of the link. + out state: LinkState; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + /// Completes when a link changes its state. + async_call WaitForLinkState { + /// The interface for which a link state shall be awaited. + in interface: InterfaceId; + + /// If not `null`, the operation completes when the link becomes `desired`. + /// Otherwise, the operation completes on the next interface state change. + in desired: ?LinkState; + + /// The new link state. + out current: LinkState; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + } + + /// Sends an ICMP or ICMPv6 echo request. + /// + /// NOTE: This can be used to test if a host is reachable. + async_call Ping { + /// The interface that shall send the ping message. + /// + /// NOTE: May be `InterfaceId.any` to use the routing table + /// to determine which interface and gateway shall be used. + in interface: InterfaceId; + + /// The IP that should be tested. + /// + /// NOTE: `target.type` decides which underlying protocol shall be used. + in target: IP; + + /// Maximum number of hops before the operation times out. + in ttl: u8; + + /// The deadline for the operation. + /// + /// The operation returns the `Timeout` error when `timeout` is smaller than `clock.monotonic()`. + /// + /// LORE: In contrast to many other overlapped operations, a `Ping` would potentially + /// never complete and it's an expected outcome that no response is received. + /// Thus, the general rule of "no timeouts in overlapped operations" is broken + /// here on purpose. + in timeout: clock.Absolute; + + /// The payload of the ICMP/ICMPv6 echo request that is sent with the message. + /// + /// NOTE: This buffer must stay valid until the end of the operation. + in payload_request: bytestr; + + /// The payload of the ICMP/ICMPv6 echo response that may be received. + /// + /// NOTE: This buffer must stay valid until the end of the operation. + /// + /// NOTE: `payload_response.len` must not be smaller than `payload_request.len`. + /// + /// NOTE: This buffer will only include the echoed payload. + in payload_response: bytebuf; + + /// The IP address which finally answered our echo request. + /// + /// NOTE: For responses from link-local IPv6 addresses, `IPv6.scope` is set + /// to the interface that received the echo response. + out responder: IP; + + /// The timestamp when the kernel received the echo reply. + out received_at: clock.Absolute; + + /// Actual number of bytes sent from `payload_request`. + /// + /// NOTE: This may be smaller than `payload_request.len` when the message is truncated. + out request_len: usize; + + /// Number of bytes received from `responder`. + out response_len: usize; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// No response was received until `timeout`. + error Timeout; + + /// The ping operation yielded an ICMP error instead of an ICMP echo. + error IcmpError; + + /// The kernel does not know a route to `target`. + error MissingRoute; + + /// A parameter was badly specified. + /// + /// This may be due to: + /// - `payload_request.len` > `payload_response.len`. + /// - `target` is an link-local IPv6 address with a `scope` that is not `interface`. + error InvalidValue; + + /// The IPv4 or IPv6 network subsystem is disabled and the `target` type can't be used on the effective interface. + error SubsystemDisabled; + + /// `interface` is down and cannot send any data. + error LinkDown; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + error SystemResources; + } +} + +/// Neighbor discovery / neighbor cache (ARP for IPv4, NDP for IPv6). +namespace neighborhood { + /// Enumerates potential origins for a network neighbor. + enum Origin : u8 { + /// The neighbor was manually created with `add_neighbor`. + item manual = 0; + + /// The neighbor was discovered by the kernel through an ARP/NDP request. + item learned = 1; + + /// The IP address was created automatically by the kernel through + /// configuration files. + item autoconfig = 2; + } + + /// A neighbor is the mapping of an IP address to a physical address. + /// + /// The following invariants apply to the timestamp values: + /// - `last_updated` >= `known_since` + /// - `expires_after` >= `known_since` + /// - `fresh_until` >= `known_since` + /// - `fresh_until` <= `expires_after` + /// - `fresh_until` >= `last_updated` if `state == State.reachable` + struct Neighbor { + /// The interface on which the neighbor was discovered. + field interface: link.InterfaceId; + + /// The IP address that identifies the neighbor. + /// + /// NOTE: Depending on `ip.type`, the following protocols were used for discovery: + /// - IPv4: ARP + /// - IPv6: NDP + /// + /// NOTE: If this IP is a link-local IPv6 ip, its scope must be equal to `interface`. + field ip: IP; + + /// The physical address that was discovered. + /// + /// NOTE: This value is an optional `link.PhysicalAddress` with the following rules: + /// - If `state` is `State.reachable`, `physical.type` is never `link.PhysicalAddress.Type.null`. + /// - Else, the `physical.type` always is `link.PhysicalAddress.Type.null`. + field physical: link.PhysicalAddress; + + /// The state defines if a neighbor is usable or not. + /// + /// NOTE: Can never have a higher numeric value than `enumerate_neighbors.max_state` + /// when received through enumeration. + field state: State; + + /// The source which discovered the neighbor. + /// + /// NOTE: When the neighbor is added with `add_neighbor`, this value is ignored + /// and `Origin.manual` is used. + field origin: Origin; + + /// Flags storing additional information about this neighbor. + field flags: Flags; + + /// The timestamp when this neighbor entry was added to the list. + /// + /// NOTE: This field allows computing the age of the neighbor. + /// + /// NOTE: The other timestamps will always be at least `known_since`. + field known_since: clock.Absolute; + + /// The timestamp when this neighbor entry was refreshed last. + /// + /// NOTE: This allows deriving a liveness for the neighbor. + field last_updated: clock.Absolute; + + /// The timestamp until which this neighbor is assumed to be valid. + /// + /// NOTE: For IPv4/ARP, the lifetime is computed from a kernel configuration. + /// + /// NOTE: For IPv6/NDP, this is derived from the ReachableTime. + /// + /// NOTE: As long as `state == State.reachable`, this value is never less + /// than `last_updated`. + /// + /// NOTE: When `clock.monotonic` returns a value bigger than this, the neighbor has + /// become stale. + /// This means the neighbor isn't necessarily valid anymore, but the kernel assumes + /// it's still usable. + /// The effect of this is that the kernel will still attempt to directly communicate + /// with the neighbor without awaiting a neighbor discovery, but it will trigger + /// an asynchronous re-discovery to ensure the neighbor still exists. + field fresh_until: clock.Absolute; + + /// The drop-dead time after which this neighbor is killed. + /// + /// NOTE: The kernel will automatically remove the entry as soon + /// as `clock.monotonic` reaches this value. + field expires_after: clock.Absolute; + + /// Flags that further specify the neighbors state. + bitstruct Flags : u8 { + /// This neighbor is believed to be an IPv6 capable router on the link. + /// + /// NOTE: This value may change over time based on observed communications on the network. + /// + /// NOTE: For IPv4 neighbors, this value is always `false`. + field is_router: bool; + + /// This neighbor is proxied through network segments. + /// + /// Set only when the kernel knows ND proxying is in effect for this mapping (e.g., learned through ND-proxy mechanisms / configuration). Otherwise the value is false. + /// + /// NOTE: For IPv4 neighbors, this value is always `false`. + field is_proxy: bool; + + reserve u6 = 0; + } + + /// Enumeration of potential states a neighbor has. + /// + /// NOTE: The numeric value of a state is a "usefulness" order and + /// is used by `enumerate_neighbors` to filter states. + enum State : u8 { + /// The neighbor is actively reachable and valid. + /// + /// This means the kernel will immediately use the physical address without performing + /// a request first. + /// + /// NOTE: When `clock.monotonic` returns a value bigger than `fresh_until`, the kernel + /// will perform an automatic background discovery of the neighbor to check if + /// neighbor is still valid. + /// + /// NOTE: When this state is active, `Neighbor.physical.type` is never `link.PhysicalAddress.Type.null`. + item reachable = 0; + + /// A neighbor discovery was executed and the neighbor could not be found. + /// + /// NOTE: This means the kernel will return an error for operations using this + /// neighbor address until `clock.monotonic` reaches `expires_after`. + /// + /// The next request after the `expires_after` is reached will trigger a new discovery process. + /// + /// NOTE: When this state is active, `Neighbor.physical.type` is `link.PhysicalAddress.Type.null`. + item failed = 1; + + /// The neighbor was requested, but isn't `reachable` nor `failed` yet. + /// + /// NOTE: This state is only set until the kernel has performed the first discovery. + /// It does not represent the background discovery. + /// + /// NOTE: When this state is active, `Neighbor.physical.type` is `link.PhysicalAddress.Type.null`. + item resolving = 2; + } + } + + /// Enumerates the currently known neighborhood for an interface. + syscall enumerate_neighbors { + /// The interface for which we want to receive the neighbors + /// or `link.InterfaceId.any` to enumerate the neighbors of all interfaces. + in interface: link.InterfaceId; + + /// Defines for which IP protocols the neighbors should be returned. + /// + /// NOTE: If `IP.Type.any` is passed, both IPv4 and IPv6 neighbors are returned, + /// assuming the corresponding subsystem is enabled. + in protocol: IP.Type; + + /// A buffer that receives the neighbors of `interface` or `null` to query + /// the total amount of neighbors. + in neighbors: ?[]Neighbor; + + /// Defines the maximum integer state value this enumeration returns. + /// This effectively allows filtering the returned list for: + /// - `Neighbor.State.reachable`: Only reachable entries are returned. This is the "true" neighborhood as currently known. + /// - `Neighbor.State.failed`: Only entries with a well-defined state are returned. This also yields knowledge about who is currently not our neighbor. + /// - `Neighbor.State.resolving`: All entries are returned. This allows querying if we're currently searching for a specific neighbor. + in max_state: Neighbor.State; + + /// The number of items written to `neighbors` if not `null`, otherwise + /// the total number of neighbors returned by the query. + out count: usize; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// Returned when `interface` is not `link.InterfaceId.any` and: + /// - `protocol` is `IP.Type.ipv4` and the ipv4 subsystem is disabled for `interface`. + /// - `protocol` is `IP.Type.ipv6` and the ipv6 subsystem is disabled for `interface`. + /// - `protocol` is `IP.Type.any` and both the ipv4 and ipv6 subsystem are disabled for `interface`. + error SubsystemDisabled; + } + + /// Queries a single neighbor. + /// + /// NOTE: `neighbor.state` must be queried to check reachability for a given + /// IP, as the neighbor might have any possible state. + syscall get_neighbor { + /// The interface for which the neighbor should be queried, or + /// or `link.InterfaceId.any` to query the neighbor on all interfaces. + in interface: link.InterfaceId; + + /// The IP address to query. + /// + /// NOTE: If `interface` is not `link.InterfaceId.any` and this IP is a link-local IPv6 ip, + /// its scope must be equal to `interface`. + /// + /// NOTE: If `interface` is `link.InterfaceId.any` and this IP is a link-local IPv6 ip, + /// its scope is used for `interface`. + in ip: IP; + + /// The neighbor entry for `ip`. + out neighbor: Neighbor; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// `ip` is an IPv6 address, but the scope is not suitable for `interface`. + error InvalidValue; + + /// The requested neighbor is not available in the neighborhood. + error NotAvailable; + + /// The `ip` is available on more than one `interface`. + /// + /// NOTE: This error can only happen when `interface` is `link.InterfaceId.any`. + error Conflict; + + /// Returned when the associated subsystem of `ip.type` is disabled for `interface`. + /// NOTE: Can never be returned if `interface == link.InterfaceId.any`. If no interface + /// with an enabled subsystem for `ip.type` has `ip`, `NotAvailable` is returned.” + error SubsystemDisabled; + } + + /// Adds a new static neighbor to the neighborhood. + /// + /// NOTE: The kernel will do the following transformations on `neighbor`: + /// - `neighbor.state = Neighbor.State.reachable` if `neighbor.physical.type != link.PhysicalAddress.Type.null` + /// - `neighbor.state = Neighbor.State.failed` if `neighbor.physical.type == link.PhysicalAddress.Type.null` + /// - `neighbor.origin = Origin.manual` + /// - `neighbor.known_since = clock.monotonic()` + /// - `neighbor.last_updated = clock.monotonic()` + /// - `neighbor.fresh_until = neighbor.expires_after` + /// + /// NOTE: `neighbor` will be validated *after* the transformations are applied. + syscall add_neighbor { + /// The neighbor to add. + + in neighbor: Neighbor; + + /// Defines that if a neighbor with the same IP already exists, + /// it is implicitly replaced with `neighbor`. + in upsert: bool; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The neighbor does not encode a correct value. + /// + /// In addition to the general validity rules of `Neighbor`, the following rules apply: + /// - `neighbor.interface` must not be `link.InterfaceId.any`. + /// - `neighbor.ip` must be a valid IP address. + /// - `neighbor.ip.scope` must be `neighbor.interface` if `neighbor.ip` is a link-local IPv6 address. + /// - `neighbor.expires_after` must not be in the past. + error InvalidValue; + + /// `upsert` is `false` and a neighbor with `neighbor.ip` already exists. + error Conflict; + + /// Returned when the associated subsystem of `neighbor.ip.type` is disabled for `neighbor.interface`. + error SubsystemDisabled; + + error SystemResources; + } + + /// Removes a single neighbor from the neighborhood. + /// + /// NOTE: If the neighbor with `ip` was created through + /// neighbor discovery, `include_learned` must be + /// set, otherwise `Forbidden` is returned as an error. + /// + /// NOTE: If no neighbor with `ip` exists, this syscall is idempotent. + syscall remove_neighbor { + /// The interface for which the neighbor should be removed. + in interface: link.InterfaceId; + + /// The neighbor to be removed. + /// + /// NOTE: If this IP is a link-local IPv6 ip, its scope must be equal to `interface`. + in ip: IP; + + /// Guardrail to prevent accidental removal of neighbors created + /// through neighbor discovery. + in include_learned: bool; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// `ip` is an IPv6 address, but the scope is not suitable for `interface`. + error InvalidValue; + + /// If `include_learned` is `false` and the neighbor to remove has + /// `Neighbor.origin == Origin.learned`. + /// + /// NOTE: Will never happen if no neighbor with `ip` exists. + error Forbidden; + + /// Returned when: + /// - `ip.type` is `IP.Type.ipv4` and the ipv4 subsystem is disabled for `interface`. + /// - `ip.type` is `IP.Type.ipv6` and the ipv6 subsystem is disabled for `interface`. + error SubsystemDisabled; + } + + /// Invalidates the neighbor table for `interface` and removes all items for `protocol`. + syscall flush_neighbors { + /// The interface for which the neighborhood should be flushed. + /// If `link.InterfaceId.any` is passed, all neighborhoods for all interfaces are flushed. + in interface: link.InterfaceId; + + /// Defines for which IP protocols the neighbors should be flushed. + /// + /// NOTE: If `IP.Type.any` is passed, both IPv4 and IPv6 neighbors are flushed, + /// assuming the corresponding subsystem is enabled. + in protocol: IP.Type; + + /// If `true`, will only remove the neighbors with `Neighbor.origin == Origin.learned`. + in keep_manual: bool; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// Returned when the associated subsystem of `protocol` is disabled for `interface` + /// and `interface` is not `link.InterfaceId.any`. + error SubsystemDisabled; + } + + /// Resolves an IP address the the associated physical address. + /// + /// NOTE: The kernel will potentially delay sending ARP/NDP requests + /// until an internal timeout has elapsed. + /// + /// This is to prevent flooding the network with requests. + /// + /// `flood` overwrites this behavior. + /// + /// NOTE: Not every schedule of a resolve triggers a new discovery. + /// The kernel is free to fuse several scheduled `Resolve` + /// operations for the same (`interface`, `target`) groups. + /// + /// `deadline` is still respected for each individual operation. + /// + /// This does not apply to operations that have `flood` set. + async_call Resolve { + /// The interface which should query its network for the physical address. + in interface: link.InterfaceId; + + /// The IP for which the physical address should be resolved. + /// + /// NOTE: The following protocols will be used depending on `target.type`: + /// - IPv4: ARP + /// - IPv6: NDP + /// + /// NOTE: If this IP is a link-local IPv6 ip, its scope must be equal to `interface`. + in target: IP; + + /// The deadline for the operation. + /// + /// The operation returns the `Timeout` error when `deadline` is smaller than `clock.monotonic()`. + /// + /// LORE: In contrast to many other overlapped operations, a `Resolve` would potentially + /// never complete and it's an expected outcome that no response is received. + /// Thus, the general rule of "no timeouts in overlapped operations" is broken + /// here on purpose. + in deadline: clock.Absolute; + + /// Overwrites the kernels internal flood protection and immediately starts + /// sending a request. + /// + /// NOTE: Setting `flood` disables the internal fusing and the kernel will trigger + /// many ARP/NDP requests. + in flood: bool; + + /// The information received by the resolver. + out neighbor: Neighbor; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// No response was received until `deadline`. + error Timeout; + + /// `target` was an invalid IP or + /// `target` is a link-local IPv6 address with `target.scope != interface`. + error InvalidValue; + + /// The associated subsystem for `target.type` is disabled on `interface`. + error SubsystemDisabled; + + /// `interface` is down and cannot send any data. + error LinkDown; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + error SystemResources; + } + + //? TODO: Add async_call WaitForUpdate when a proper model for the wait operation was found. +} + +/// IPv6 Stateless Address Autoconfiguration (SLAAC). +namespace slaac { + + //? TODO: Add support for RFC 4191 ( Default Router Preferences and More-Specific Routes) + + /// Represents a single Prefix Information Option (PIO) learned from an IPv6 Router Advertisement. + /// + /// NOTE: The following invariants apply to the timestamp types: + /// - `preferred_until` >= `received_at` + /// - `updated_at` >= `received_at` + /// - `valid_until` >= `received_at` + /// - `valid_until` >= `preferred_until` + /// - `valid_until` >= `updated_at` + struct Prefix { + /// The router that announced the prefix. + /// + /// NOTE: This address is typically a link-local address with the associated interface for `IPv6.scope`. + /// + /// NOTE: If router is link-local, `router.scope` must be the associated interface. + field router: IPv6; + + /// The prefix announced by the router. + /// + /// NOTE: `prefix.scope` must be `link.InterfaceId.any`. + /// + /// NOTE: All bits beyond `prefix_len` are zeroed out to enable determinism. + field prefix: IPv6; + + /// The number of bits inside `prefix` that are part of the prefix. + /// NOTE: Can be between 0 and 128 inclusive. + field prefix_len: u8; + + /// Defines additional information for this prefix. + field flags: Flags; + + /// The timestamp when the kernel initially received this prefix. + field received_at: clock.Absolute; + + /// The timestamp when the kernel last received this prefix. + field updated_at: clock.Absolute; + + /// Timestamp until which the kernel will prefer this prefix and derived IP addresses. + /// + /// NOTE: The prefix is still valid until `valid_until`, but the network stack should choose + /// another prefix if possible. + field preferred_until: clock.Absolute; + + /// Timestamp at which the kernel will drop the prefix. + field valid_until: clock.Absolute; + + bitstruct Flags : u8 { + /// The prefix is reachable directly through this interface without + /// the need of a gateway. + /// + /// NOTE: If the slaac subsystem is enabled on the associated interface, the kernel will + /// automatically add (or upsert) a on-link prefix route for this prefix with: + /// - `link.Route.network` set to `Prefix.prefix`. + /// - `link.Route.prefix_len` set to `Prefix.prefix_len`. + /// - `link.Route.gateway` set to the unspecified address (`::`). + /// - `link.Route.interface` set to the associated interface of the `Prefix`. + /// - `link.Route.priority` set to `0`. + /// - `link.Route.origin` set to `link.RouteOrigin.slaac`. + /// - `link.Route.valid_until` set to a value depending on the context. + /// + /// If multiple `Prefix` entries exist for the same + /// (`interface`, `prefix`, `prefix_len`) (i.e. advertised by different routers), + /// the derived on-link prefix route remains present as long as at least one matching + /// `Prefix` entry with `on_link = true` is still valid. + /// + /// The kernel sets `link.Route.valid_until` to the maximum `valid_until` + /// across all currently valid matching `Prefix` entries with `on_link = true`. + /// + /// If the set of matching prefixes changes (update/expiry/flush), + /// the kernel recomputes `link.Route.valid_until` accordingly and removes the + /// derived route once no matching prefixes remain. + /// + /// NOTE: The kernel will only create/update routes with `link.Route.origin = link.RouteOrigin.slaac` + /// and will not modify routes of other origins. + field on_link: bool; + + /// The interface shall automatically derive IP addresses from the prefix. + /// + /// NOTE: See `Config` on how this bit will be used. + /// + /// NOTE: The kernel will automatically derive addresses from the prefix when the slaac subsystem + /// is enabled and `prefix_len` is 64. + /// + /// The derived addresses will be added with `link.AddressBinding.origin` + /// set to `link.AddressOrigin.slaac`. + /// + /// If multiple matching `Prefix` entries exist for the same + /// (`interface`, `prefix`, `prefix_len`) with `autonomous = true`, + /// the kernel sets `link.AddressBinding.valid_until` to the maximum `valid_until` + /// across all currently valid matching entries, and recomputes it on + /// update/expiry/flush. The address is removed once no matching prefixes remain. + /// + /// NOTE: The kernel will only create/update address bindings with `link.AddressBinding.origin = link.AddressOrigin.slaac` + /// and will not modify address bindings of other origins. + field autonomous: bool; + + reserve u6 = 0; + } + } + + /// Represents the router identity from a single Router Advertisement. + /// Also contains neighborhood discovery parameters sent with the Router Advertisement. + /// + /// NOTE: The following invariants apply to the timestamp types: + /// - `valid_until` >= `received_at` + /// - `valid_until` >= `updated_at` + /// - `updated_at` >= `received_at` + struct Router { + /// The address of the discovered router. + /// + /// NOTE: If the slaac subsystem is enabled for the interface, a default route + /// will be upserted that uses `address` as the next-hop: + /// + /// - `link.Route.network` set to the unspecified address (`::`). + /// - `link.Route.prefix_len` set to `0`. + /// - `link.Route.gateway` set to `Router.address`. + /// - `link.Route.interface` set to the associated interface for this router. + /// - `link.Route.priority` set to `0`. + /// - `link.Route.valid_until` set to `Router.valid_until`. + /// - `link.Route.origin` set to `link.RouteOrigin.slaac`. + /// + /// NOTE: If this IP is a link-local IPv6 IP, its scope must be equal to associated interface. + /// + /// NOTE: The kernel will only create/update routes with `link.Route.origin = link.RouteOrigin.slaac` + /// and will not modify routes of other origins. + field address: IPv6; + + /// The max. number of hops for outbound packets via this router. + /// + /// NOTE: 0 means the router did not advertise a hop limit and the + /// actual value is unknown. + field hop_limit: u8; + + /// The MTU for the link. + /// + /// NOTE: 0 means the router did not advertise an MTU and the + /// actual value is unknown. + field mtu: u32; + + /// The time in milliseconds a neighbor should be considered reachable. + /// + /// NOTE: If zero, a default value shall be used (typically 30 seconds). + /// + /// NOTE: This value affects how `neighborhood.Neighbor.fresh_until` is derived. + field reachable_time_ms: u32; + + /// Time between retransmitted Neighbor Solicitation messages in milliseconds. + /// + /// This affects neighbor discovery retries (including DAD). + /// + /// NOTE: If zero, the router did not specify a value and the kernel uses its default. + field retrans_time_ms: u32; + + /// Defines additional information for this router. + field flags: Flags; + + /// The timestamp at which the kernel received this Router Advertisement. + field received_at: clock.Absolute; + + /// The timestamp when the kernel last received this Router Advertisement. + field updated_at: clock.Absolute; + + /// The timestamp at which the kernel will automatically remove the router. + field valid_until: clock.Absolute; + + bitstruct Flags : u8 { + /// If set, defines that DHCPv6 shall be used to obtain addresses. + /// + /// NOTE: If set, and the dhcp6 subsystem is enabled for the interface, the kernel + /// will automatically perform the DHCPv6 requests when the Router Advertisement + /// is received. + field managed: bool; + + /// If set, defines that additional configuration like DNS servers shall be obtained through + /// DHCPv6. + /// + /// NOTE: If set, and the dhcp6 subsystem is enabled for the interface, the kernel + /// will automatically perform the DHCPv6 requests when the Router Advertisement + /// is received. + field other_config: bool; + + reserve u6 = 0; + } + } + + //? Configuration: + + /// Enumeration of potential methods for stable address generation. + /// + /// NOTE: Addresses generated with these methods are reboot-safe + /// and will be stable for the system. + enum StableAddressGeneration : u8 { + /// Don't generate an address at all. + item none = 0; + + /// Derives stable addresses from the physical link address. + /// + /// NOTE: This method is best-effort and may use other sources to + /// generate reboot-stable IDs for the interface. + item eui64 = 1; + + /// Derives stable addresses in a privacy-preserving manner using a + /// stable secret on the system. + item rfc7217 = 2; + } + + /// Enumeration of potential methods for temporary address generation. + /// + /// NOTE: Addresses generated with these methods are ephemeral and may + /// be rotated after a certain time. + enum TemporaryAddressGeneration : u8{ + /// Don't generate an address at all. + item none = 0; + + /// Temporary addresses are generated in a (pseudo) random pattern. + item rfc8981 = 1; + } + + /// SLAAC configuration for an interface. + struct Config { + /// The currently used stable address generation method to automatically derive + /// addresses when requested. + /// + /// NOTE: The default values is `StableAddressGeneration.eui64` unless + /// changed by a kernel configuration. + field stable_method: StableAddressGeneration; + + /// The currently used temporary address generation method to automatically derive + /// addresses when requested. + /// + /// NOTE: The default values is `TemporaryAddressGeneration.none` unless + /// changed by a kernel configuration. + field temp_method: TemporaryAddressGeneration; + } + + /// Queries the currently enabled configuration for the given interface. + /// + /// NOTE: This syscall will also work when the slaac subsystem is disabled, + /// to enable setting a policy before activating the subsystem. + syscall get_config { + /// The interface for which the configuration shall be returned. + in interface: link.InterfaceId; + + /// The currently active configuration. + out config: Config; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + /// Changes the currently enabled configuration for the given interface. + /// + /// NOTE: This will not re-generate addresses derived with the previous configuration. + /// + /// NOTE: This syscall will also work when the slaac subsystem is disabled, + /// to enable setting a policy before activating the subsystem. + syscall set_config { + /// The interface for which the SLAAC configuration shall be updated. + in interface: link.InterfaceId; + + /// The new configuration, replacing the previous one. + in config: Config; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + } + + //? Status Query: + + /// A summary of the SLAAC status for an interface. + struct Status { + /// The timestamp when the kernel last received a Router Advertisement + /// or a `Router`/`Prefix` expired. + field last_update: clock.Absolute; + + /// Additional boolean properties + field flags: Flags; + + bitstruct Flags : u16 { + /// `true` if at least a single `Prefix.flags.on_link` is set. + field on_link: bool; + + /// `true` if at least a single `Prefix.flags.autonomous` is set. + field autonomous: bool; + + /// `true` if at least a single `Router.flags.managed` is set. + field managed: bool; + + /// `true` if at least a single `Router.flags.other_config` is set. + field other_config: bool; + + reserve u12 = 0; + } + } + + /// A quick status query to get the current SLAAC status. + /// + /// LORE: This syscall primarily exists to prevent userland + /// to enumerate all properties just to learn some trivial + /// information. + syscall get_status { + /// The interface for which the status shall be queried. + in interface: link.InterfaceId; + + /// The current status of `interface`. + out status: Status; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// Returned when the SLAAC subsystem of `interface` is disabled. + error SubsystemDisabled; + } + + //? Queries: + + /// Enumerates the routers learned through SLAAC for `interface`. + syscall enumerate_routers { + /// The interface for which the routers shall be queried. + in interface: link.InterfaceId; + + /// A buffer that will receive the enumerated routers. + /// + /// NOTE: If `null`, no routers will be enumerated, but only the total + /// count is returned. + in routers: ?[]Router; + + /// Total number of available routers. + /// + /// NOTE: If smaller than `routers.len`, the elements for `routers[count..]` will + /// be left unchanged. + out count: usize; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// Returned when the SLAAC subsystem of `interface` is disabled. + error SubsystemDisabled; + } + + /// Enumerates the prefixes learned through SLAAC for `interface`. + syscall enumerate_prefixes { + /// The interface for which the prefixes shall be queried. + in interface: link.InterfaceId; + + /// A buffer that will receive the enumerated prefixes. + /// + /// NOTE: If `null`, no prefixes will be enumerated, but only the total + /// count is returned. + in prefixes: ?[]Prefix; + + /// Total number of available prefixes. + /// + /// NOTE: If smaller than `prefixes.len`, the elements for `prefixes[count..]` will + /// be left unchanged. + out count: usize; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// Returned when the SLAAC subsystem of `interface` is disabled. + error SubsystemDisabled; + } + + /// Defines what parts to flush. + bitstruct FlushMode : u8 { + /// If `true`, removes all routers from the selected interface. + /// + /// NOTE: This implies that all "default" routes added for this + /// interface through the SLAAC subsystem will also be removed. + field routers: bool; + + /// If `true`, removes all prefixes from the selected interface. + /// + /// NOTE: This implies that all on-link prefix routes added for this + /// interface through the SLAAC subsystem will also be removed. + field prefixes: bool; + + reserve u6 = 0; + } + + /// Flushes the SLAAC state for a given interface. + /// + /// NOTE: A flush counts as an "update event" and will change the + /// `Status.last_update` field to `clock.monotonic()`. + syscall flush { + /// The interface for which the SLAAC state shall be flushed. + in interface: link.InterfaceId; + + /// Defines how to flush the state. + /// + /// NOTE: If all bits inside mode are zero, `InvalidValue` is returned. + in mode: FlushMode; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// Returned when the SLAAC subsystem of `interface` is disabled. + error SubsystemDisabled; + + /// All bits inside `mode` were zero. + error InvalidValue; + } + + /// Forces the refresh of the router advertisements by sending a + /// Router Solicitation. + /// + /// NOTE: The operation will complete when at least a single Router Advertisement + /// was received. + /// + /// NOTE: Not every schedule of a refresh triggers a new Router Solicitation. + /// The kernel is free to fuse several scheduled `Refresh` + /// operations for the same `interface`. + /// + /// `deadline` is still respected for each individual operation. + /// + /// NOTE: If `interface` has no suitable link-local address available, the kernel + /// will send the Router Solicitation from the unspecified address (`::`). + /// If multiple suitable addresses are available, the kernel will choose + /// one in a stable manner: The source address will be the same until the + /// address bindings of `interface` change. + async_call Refresh { + /// The interface for which the SLAAC state shall be refreshed. + in interface: link.InterfaceId; + + /// The deadline for the operation. + /// + /// The operation returns the `Timeout` error when `deadline` is smaller than `clock.monotonic()`. + /// + /// LORE: In contrast to many other overlapped operations, a `Refresh` would potentially + /// never complete and it's an expected outcome that no response is received. + /// Thus, the general rule of "no timeouts in overlapped operations" is broken + /// here on purpose. + in deadline: clock.Absolute; + + /// The new status of `interface`. + out status: Status; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// No response was received until `deadline`. + error Timeout; + + /// `deadline` is not in the future. + error InvalidValue; + + /// Returned when the SLAAC subsystem of `interface` is disabled. + error SubsystemDisabled; + + /// `interface` is down and cannot send any data. + error LinkDown; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + error SystemResources; + } + + /// This operation completes when `Status.last_update` changes. + async_call WaitForUpdate { + /// The interface for which an update shall be awaited. + in interface: link.InterfaceId; + + /// The new SLAAC status of `interface`. + out new_status: Status; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// Returned when the SLAAC subsystem of `interface` is disabled. + error SubsystemDisabled; + + error SystemResources; + } +} + +/// Everything related to DHCP management for IPv4. +namespace dhcp4 { + + /// NOTE: These options are defined in RFC 2132. + enum OptionCode : u8 { + //? TODO: Add documentation for all options including their contents and size constraints based on the information from RFC 2132. + + item pad = 0; + item subnet_mask = 1; + item time_offset = 2; + item router = 3; + + item domain_name_server = 6; + + item ip_address_lease_time = 51; + + item server_identifier = 54; + + item renewal_time_value = 58; + + item rebinding_time_value = 59; + + item end = 255; + //? TODO: fill out the rest of the options and document their meaning and use + ... + } + + /// A DHCP option. + struct Option { + /// The type of option + field code: OptionCode; + + /// The value of the option. + /// + /// NOTE: Maximum length of `value` is 255. + /// + /// NOTE: The length of the value must match the legal values for `code`. + /// + /// NOTE: The lifetime of this value for fields in a lease is documented on `Lease` itself. + /// + /// NOTE: Options passed to `RequestLease` will be copied by the kernel in the schedule operation. + field value: []const u8; + } + + /// A DHCP lease. + /// + /// NOTE: Any pointer to an array inside the lease is valid until one of the invalidation events occur: + /// - The interface for the lease is removed from the kernel. + /// - The lease is automatically refreshed by the kernel (may only happen if the dhcp4 subsystem is enabled). + /// - A `ReleaseLease` is scheduled for the owning interface. + /// - A `RequestLease` completes for the owning interface. + /// + /// These events can only happen during a scheduler yield, so the pointers may be dead + /// when the thread yields. + /// + /// In general, an application should not hold onto `Lease` information for longer + /// than technically necessary. + struct Lease { + /// The server that issued the DHCP lease. + /// + /// NOTE: This is either the value sent with `OptionCode.server_identifier` + /// or if the option does not exist, the IP from which the DHCPACK was sent. + field server: IPv4; + + /// The address issued by the DHCP server. + /// + /// NOTE: If the lease is acquired with DHCPINFORM this value will be the + /// unspecified address (`0.0.0.0`). + field address: IPv4; + + /// The prefix length of the network for `address`. + /// + /// NOTE: This value is derived from the value sent with `OptionCode.subnet_mask` + /// or 32 if the option is not present. + /// + /// NOTE: If the lease is acquired with DHCPINFORM this value will be zero. + field prefix_len: u8; + + /// The list of announced routers by the DHCP server. + /// + /// NOTE: This list will be derived from the value sent with `OptionCode.router`. + /// + /// NOTE: The lifetime of this array is documented on `Lease` itself. + field routers: []const IPv4; + + /// The list of DNS servers provided by the DHCP server. + /// + /// NOTE: This list will be derived from the value sent with `OptionCode.domain_name_server`. + /// + /// NOTE: The lifetime of this array is documented on `Lease` itself. + field dns_servers: []const IPv4; + + /// The complete list of options sent by the DHCP server. + /// + /// NOTE: The order inside this array is the same as it was sent by the server. + /// + /// NOTE: The lifetime of this array is documented on `Lease` itself. + /// + /// NOTE: The kernel will remove all options with `OptionCode.pad` and `OptionCode.end`. + field options: []const Option; + + /// The timestamp when the kernel applied the DHCPACK message that issued + /// this lease. + field issued_at: clock.Absolute; + + /// The timestamp when the lease should be renewed (T1). + /// + /// NOTE: If the lease was created by `DHCPINFORM`, the value will be set to + /// `clock.Absolute.infinity`. + /// + /// NOTE: If present, will be set from `OptionCode.renewal_time_value`, otherwise + /// will use the default value of 50% of the total lease time. + /// + /// NOTE: If no lease time can be derived, `clock.Absolute.infinity` is used. + field renew_at: clock.Absolute; + + /// The timestamp when the lease should be rebound (T2). + /// + /// NOTE: If the lease was created by `DHCPINFORM`, the value will be set to + /// `clock.Absolute.infinity`. + /// + /// NOTE: If present, will be set from `OptionCode.rebinding_time_value`, otherwise + /// will use the default value of 87.5% of the total lease time. + /// + /// NOTE: If no lease time can be derived, `clock.Absolute.infinity` is used. + field rebind_at: clock.Absolute; + + /// The timestamp after which the lease is not valid anymore. + /// + /// NOTE: If the lease was created by `DHCPINFORM`, the value will be set to + /// `clock.Absolute.infinity`. + /// + /// NOTE: If present, will be set from `OptionCode.ip_address_lease_time`, otherwise + /// will be set to the requested address lease time if present in `RequestLease.options`, + /// or otherwise will be set to `clock.Absolute.infinity` to mark that no lease time was + /// issued. + field valid_until: clock.Absolute; //? when does the lease expire + } + + /// Enumerations of how the kernel will handle the received lease of `RequestLease`. + enum AutoUpdateMode : u8 { + /// The kernel will not do anything with the received lease. + /// + /// NOTE: This can be used to perform a DHCP request without directly changing network + /// configuration. + item disabled = 0; + + /// The kernel will only use the `Lease.address` and `Lease.prefix_len` to + /// perform an automatic `link.AddAddress` operation on `RequestLease.interface`. + /// + /// This includes: + /// - Automatic adding/updating of the received IP address to `interface`. + /// - Automatic creation/update of the connected route for `interface`. + item address_only = 1; + + /// In addition to the effects of `address_only`, the kernel will also upsert + /// a route to the network `0.0.0.0/0` through `RequestLease.interface` for + /// all received routers. + /// + /// If multiple routers are advertised, the `Route.priority` will be staged + /// such that the last router has priority 1 and each previous router will + /// have a priority 1 higher. + /// + /// This means that the first advertised router has the highest priority (which is + /// set to the number of routers). + item address_and_route = 2; + + /// In addition to the effects of `address_only`, the kernel will also upsert + /// the received DNS servers associated with `RequestLease.interface`. + item full = 3; + } + + /// Requests new DHCP lease. Completes when at least a single server has successfully + /// provided a DHCP lease or the operation timed out. + /// + /// NOTE: Only a single `RequestLease` or `ReleaseLease` operation can be active per `interface` at the + /// same time. + /// + /// NOTE: The kernel will always use the first successful DHCPACK when multiple options are available. + /// + /// NOTE: When multiple DHCPOFFER messages are received, the kernel will perform the DHCPREQUEST process + /// in parallel for each received option unless a DHCPACK was already received. + async_call RequestLease { + /// The network interface that shall perform the DHCP request. + in interface: link.InterfaceId; + + /// The deadline until which the whole operation has to be completed. + /// + /// LORE: In contrast to many other overlapped operations, a `RequestLease` would potentially + /// never complete and it's an expected outcome that no response is received. + /// Thus, the general rule of "no timeouts in overlapped operations" is broken + /// here on purpose. + in deadline: clock.Absolute; + + /// The unicast address of the DHCP server that shall be used to obtain the DHCP lease. + /// + /// NOTE: When the unspecified address (0.0.0.0) is passed, + /// the kernel will perform a broadcast to discover potential DHCP servers. + in server: IPv4; + + /// Additional options to send with the request. + /// + /// NOTE: The kernel omits any of its own options if `options` contains at least + /// one option with the same `Option.code`. + /// + /// This allows userland to overwrite all potentially kernel-provided options. + /// + /// NOTE: The kernel will copy the options array in `overlapped.schedule`. + /// This means the user can use the memory after successful scheduling. + /// + /// NOTE: The options in this array will be sent in exactly this order *after* + /// the kernel-sent options. + /// + /// This allows userland to emit multiple instances of the same option + /// code (some encodings support this); the kernel transmits as-is. + /// + /// NOTE: The kernel will never merge any values of this array. + in options: []const Option; + + /// If `true`, the kernel will perform a DHCPINFORM process instead of a the regular + /// DHCPDISCOVER/DHCPREQUEST process. + /// + /// This allows querying local network configuration even with a non-DHCP configured IP address. + /// + /// NOTE: This requires that we already have a statically configured IP address. + /// + /// NOTE: If `server` is the unspecified address (`0.0.0.0`), the kernel will send a + /// broadcast message for each configured IPv4 addresses for `interface`. + in inform_only: bool; + + /// Defines how the kernel will update `interface` when the request is successful. + /// + /// NOTE: If the dhcp4 subsystem is enabled, this option is ignored and the kernel will + /// handle the results like it would've performed an automatic/timed DHCP request/renew. + /// + /// This means the operation can be used as a force-refresh the DHCP lease. + /// + /// NOTE: If the dhcp4 subsystem is disabled, setting this option does not imply the + /// kernel will perform automatic renewal or rebinding of the DHCP lease. + in update_mode: AutoUpdateMode; + + /// The lease provided by the server. + /// + /// NOTE: If `inform_only` is `true`, `lease.address` is `0.0.0.0` + /// and `lease.prefix_len` is zero. + out lease: Lease; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// No response was received until `deadline`. + error Timeout; + + /// An invalid value was passed as a parameter. + /// + /// This may be due to: + /// - `server` is neither the unspecified address nor a valid unicast address. + /// - `options` contains an invalid DHCP option. + /// - `deadline` is not in the future. + error InvalidValue; + + /// Another `RequestLease` or `ReleaseLease` operation is in progress for `interface`. + /// + /// NOTE: This error may "spuriously" happen when the dhcp4 subsystem is enabled + /// and currently performs an internal DHCP operation. + error InProgress; + + /// The DHCP server offered an address that is not ours, but reachable + /// through `interface`. + /// + /// NOTE: This error implies the kernel has responeded with DHCPDECLINE. + error AddressConflict; + + /// All available DHCP servers have rejected our request with `DHCPNAK`. + error Rejected; + + /// The kernel does not know a route to `server`. + /// + /// NOTE: This error can only happen when `server` is not unspecified. + error MissingRoute; + + /// The kernel cannot send the DHCPINFORM message as `interface` has no + /// assigned IPv4 address. + error MissingSourceAddress; + + /// The IPv4 subsystem is disabled on `interface`. + error SubsystemDisabled; + + /// `interface` is down and cannot send any data. + error LinkDown; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + error SystemResources; + } + + /// Releases current lease, idempotent if none exists. + /// + /// NOTE: Only a single `RequestLease` or `ReleaseLease` operation can be active per `interface` at the + /// same time. + /// + /// NOTE: This operation will implicitly disable the dhcp4 subsystem as otherwise + /// the subsystem would immediatly try to request a new DHCP lease and `ReleaseLease` + /// would be just an implicit `RequestLease` operation. + async_call ReleaseLease { + /// The interface for which a lease should be released. + in interface: link.InterfaceId; + + /// If `true` will remove the kernel-managed lease object even in + /// the case of a communication error. + /// + /// NOTE: Each error code that will still remove the release + /// documents this. + /// + /// NOTE: If `force` is set, the scheduling implicitly cancels an + /// active `RequestLease` operation. + in force: bool; + + /// The deadline until which the whole operation has to be completed. + /// + /// LORE: In contrast to many other overlapped operations, a `RequestLease` would potentially + /// never complete and it's an expected outcome that no response is received. + /// Thus, the general rule of "no timeouts in overlapped operations" is broken + /// here on purpose. + in deadline: clock.Absolute; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + /// No response was received until `deadline`. + /// + /// NOTE: The leases was still removed when `force` was set. + error Timeout; + + /// Another `RequestLease` or `ReleaseLease` operation is in progress for `interface`. + /// + /// NOTE: This error cannot happen when `force` is set. + error InProgress; + + /// An invalid value was passed as a parameter. + /// + /// This may be due to: + /// - `deadline` is not in the future. + error InvalidValue; + + /// The IPv4 subsystem is disabled on `interface`. + /// + /// NOTE: This error implies that the interface won't have any lease anyways. + error SubsystemDisabled; + + /// `interface` is down and cannot send any data. + /// + /// NOTE: This error informs the caller that the kernel cannot + /// send the DHCPRELEASE message. + /// + /// NOTE: The leases was still removed when `force` was set. + error LinkDown; + + /// There was an i/o error that lead to the failure of this operation. + /// + /// NOTE: The leases was still removed when `force` was set. + error IoError; + + error SystemResources; + } + + /// Returns current lease or NotAvailable if none exists + syscall get_info { + /// Interface for which the lease shall be queried. + in interface: link.InterfaceId; + + /// The current lease the kernel stores for `interface`. + out info: Lease; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The kernel currently has no lease associated with `interface`. + error NotAvailable; + + /// The IPv4 subsystem is disabled on `interface`. + error SubsystemDisabled; + } + + /// Completes when new lease was set, old lease was released or expired. + /// + /// NOTE: Multiple `WaitForUpdate` can be issued which will all complete at the + /// same time when the status changes. + async_call WaitForUpdate { + /// The interface for which a new DHCP state shall be awaited. + in interface: link.InterfaceId; + + /// The new lease or `null` if the lease was removed. + out info: ?Lease; + + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; + + /// The underlying `interface` was removed during this operation. + error Gone; + + error SystemResources; + } +} + +namespace dhcp6 { + //? TODO: Design the DHCPv6 kernel API +} + +namespace dns { + //? TODO: Design the DNS kernel API + + //? TODO: Keep in mind that DNS servers can be globally set or associated with an + //? interface. Also DNS servers have a lifetime as well. +} diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi new file mode 100644 index 00000000..a8c44e64 --- /dev/null +++ b/src/abi/src/update.abi @@ -0,0 +1,286 @@ + +//? Pending: namespace gui +//? Pending: namespace io.serial +//? Pending: namespace io.i2c + + +namespace link; +enum Interface : u16 { any = 0, ... } +struct InterfaceDescription { /* type, name, vendor, bandwidth, mtu, mac, ... */ } +syscall enumerate_interfaces { /* standard pattern */ } +syscall set_default_gateway { in ?Interface, IP } +syscall get_default_gateway { out ?Interface, out IP } +syscall set_dhcp_enabled { in Interface, in v4: ?bool; in v6: ?bool } +syscall get_dhcp_enabled { in Interface, out v4: bool; out v6: bool } +syscall get_description { in Interface, out InterfaceDescription } +struct Binding { type: IP_Type, value: union { ipv4: struct { addr: IPv4, mask: IPv4 }, ipv6: IPv6 } } +syscall enumerate_addresses { in Interface, in bindings: ?[]Binding; out count; } +async_call AddAdress { in Interface, in Binding } (might require communication to external NICs) +async_call RemoveAdress { in Interface, in Binding } (might require communication to external NICs) +async_call Ping { in ?Interface; in IP; in ttl: u16; payload: bytebuf; out answer: IP; out duration: clock.Duration; out hops: u32; count: usize } +async_call RequestDhcp4Lease { in Interface; ... } +async_call RequestDhcp6Lease { in Interface; ... } +syscall get_dhcp4_info { in Interface; ... } +syscall get_dhcp6_info { in Interface; ... } + +namespace raw; +resource RawSocket { } +syscall create_raw_socket { in Interface } +async_call ReceivePacket { in RawSocket; in bytebuf; out usize; out when: clock.Absolute; } +async_call SendPacket { in RawSocket; in bytestr; } + + +Ashet OS networking model (current intent): + +- Interfaces: + - Interface IDs are u32, monotonic, stable per boot, never reused. + - Validity is ephemeral (until next yield). + - Interface disappearance yields error.Gone on dependent objects. + - Interfaces are composite objects (link + IP + routing + DHCP). + - Subsystems (IPv4, IPv6, DHCP, etc.) can be individually enabled/disabled. + +- Addressing: + - Multiple IPv4 and IPv6 addresses per interface. + - Address model is (IP address + prefix_len) for both v4 and v6. + - Adding an address implicitly creates a connected route. + - Addresses have mandatory lifetimes; AddAddress is upserting. + - Address metadata includes origin (manual, dhcpv4, dhcpv6, slaac, autoconfig). + +- Routing: + - Explicit routing table is exposed. + - Routes are (dest prefix, gateway?, interface, priority). + - Routes have origin and optional lifetime. + - Kernel auto-manages DHCP/SLAAC routes unless subsystem disabled. + - No special “default gateway” concept; defaults are 0/0 or ::/0 routes. + - Route resolution: longest prefix, then priority, then insertion order. + +- Socket ↔ interface binding: + - UDP/TCP sockets must bind to an IP; may also bind to an interface. + - Interface binding is a hard restriction. + - Address-only bind uses routing dynamically. + - Address+interface bind errors if no matching route exists. + - Raw sockets must always bind to exactly one interface. + +- Ports: + - Port scope is (IP, port, interface). + - Same IP+port allowed on different interfaces. + - No port reuse semantics (no SO_REUSE* equivalents). + +- Async operation model: + - One in-flight Send and one in-flight Receive per socket. + - UDP/TCP Bind and UDP Connect/Disconnect are synchronous. + - TCP Connect is async; multiple concurrent connects allowed (Happy Eyeballs). + - Accept is async; multiple accepts allowed on a listener. + +- TCP: + - Separate TcpListener and TcpSocket resources. + - Only full Disconnect is exposed (no half-close). + - Receive is byte-stream only, with read_all flag. + - Send requires buffer to stay valid until completion. + - Partial send/receive only occurs on FIN/RST. + - partial_reason enum distinguishes regular/short/FIN/RST. + +- UDP: + - One receive == exactly one datagram. + - Unified ReceiveFrom with optional peer filtering. + - Truncation is allowed; return written size + actual datagram size. + - Packet metadata includes arrival time and interface. + - ICMP errors are surfaced. + +- ICMP handling: + - Hard ICMP errors fail in-flight ops. + - Soft ICMP errors latch on the socket. + - Small FIFO of latched ICMP errors is kept. + - Details queried via get_last_icmp_error. + +- DNS: + - Two APIs: + - High-level resolve (A/AAAA only). + - Low-level record query. + - Always async. + - Parallel querying of multiple DNS servers. + - Kernel caching allowed; no_cache flag bypasses it. + - Per-interface DNS via interface-associated servers. + +- Raw sockets: + - Two kinds: Ethernet-level and IP-level (v4/v6). + - Ingress only; no interception. + - Raw sockets are observers, not interceptors. + - Ethernet raw sees full frames. + - IP raw parses/builds IP headers. + - Filtering supported. + +- Ping: + - ICMP Echo only. + - Single probe per call. + - Optional interface binding. + - Caller supplies TTL and timeout. + +- Errors: + - Shared base networking error vocabulary. + - Precision over convenience. + - Driver errors collapsed into IoError. + +- Resource teardown: + - Destroying sockets cancels ops immediately, no protocol close. + - Destroying interfaces yields error.Gone to dependents. + + +namespace link { + + //? TODO: field prefer_temporary: bool; + +} + +namespace dhcp6 { + + enum OptionCode : u16 { + item clientid = 1; + item serverid = 2; + item ia_na = 3; + item ia_ta = 4; + item ia_addr = 5; + item oro = 6; + item preference = 7; + item elapsed_time = 8; + item relay_message = 9; + item unicast = 12; + item status_code = 13; + item rapid_commit = 14; + item user_class = 15; + item vendor_class = 16; + item vendor_opts = 17; + item interface_id = 18; + item reconfigure_msg = 19; + item reconfigure_accept = 20; + item dns_recursive_name_server = 23; + item domain_search_list = 24; + item ia_pd = 25; + item iaprefix = 26; + ... + } + + struct Option { + field code: OptionCode; + field value: []const u8; + } + + bitstruct RequestKind : u8 { + field addresses: bool; + field prefixes: bool; + reserve u6 = 0; + } + + struct IaAddress { + field address: IPv6; + field preferred_until: clock.Absolute; + field valid_until: clock.Absolute; + } + + struct IaPrefix { + field prefix: IPv6; + field prefix_len: u8; + field preferred_until: clock.Absolute; + field valid_until: clock.Absolute; + } + + struct IdentityAssociationNA { + field iaid: u32; + field renew_at: clock.Absolute; + field rebind_at: clock.Absolute; + field addresses: []const IaAddress; + } + + struct IdentityAssociationPD { + field iaid: u32; + field renew_at: clock.Absolute; + field rebind_at: clock.Absolute; + field prefixes: []const IaPrefix; + } + + struct Lease { + field server: IPv6; + field server_duid: []const u8; + field client_duid: []const u8; + + field ia_na: []const IdentityAssociationNA; + field ia_pd: []const IdentityAssociationPD; + + field dns_servers: []const IPv6; + field options: []const Option; + + field issued_at: clock.Absolute; + field renew_at: clock.Absolute; + field rebind_at: clock.Absolute; + field valid_until: clock.Absolute; + } + + enum AutoUpdateMode : u8 { + item disabled = 0; + item addresses_only = 1; + item prefixes_only = 2; + item addresses_and_prefixes = 3; + item full = 4; + } + + async_call RequestLease { + in interface: link.InterfaceId; + in deadline: clock.Absolute; + in server: IPv6; + in request: RequestKind; + in options: []const Option; + in information_only: bool; + in update_mode: AutoUpdateMode; + + out lease: Lease; + + error InvalidInterface, Gone, Timeout, InvalidValue, InProgress, AddressConflict, Rejected, MissingRoute, MissingSourceAddress, SubsystemDisabled, LinkDown, IoError, SystemResources; + } + + async_call ReleaseLease { + in interface: link.InterfaceId; + in force: bool; + in deadline: clock.Absolute; + + error InvalidInterface, Gone, Timeout, InvalidValue, InProgress, SubsystemDisabled, LinkDown, IoError, SystemResources; + } + + syscall get_info { + in interface: link.InterfaceId; + out info: Lease; + + error InvalidInterface, NotAvailable, SubsystemDisabled; + } + + async_call WaitForUpdate { + in interface: link.InterfaceId; + out info: ?Lease; + + error InvalidInterface, Gone, SystemResources; + } +} + + +namespace neighborhood { + + /// Enumerates the ways the kernel can update/edit the list of neighbours. + enum UpdateKind : u8 { + item added = 0; + item removed = 1; + item changed = 2; + item expired = 3; + item flushed = 4; + } + + /// Waits until the kernel updates/edits the list of neighbors. + async_call WaitForUpdate { + in interface: link.InterfaceId; + + /// NOTE: Cannot be `IP.Type.any`. + in protocol: IP.Type; + out kind: UpdateKind; + out entry: Entry; + + error InvalidInterface, Gone, SystemResources, SubsystemDisabled; + } +} diff --git a/vendor/ashet-os-vscode/package-lock.json b/vendor/ashet-os-vscode/package-lock.json index b9e48ba2..ba851251 100644 --- a/vendor/ashet-os-vscode/package-lock.json +++ b/vendor/ashet-os-vscode/package-lock.json @@ -11,7 +11,7 @@ "@vscode/vsce": "^3.5.0" }, "engines": { - "vscode": "^1.100.0" + "vscode": "^1.91.0" } }, "node_modules/@azu/format-text": { diff --git a/vendor/ashet-os-vscode/package.json b/vendor/ashet-os-vscode/package.json index daea9eb1..1e4d84c2 100644 --- a/vendor/ashet-os-vscode/package.json +++ b/vendor/ashet-os-vscode/package.json @@ -4,7 +4,7 @@ "description": "Support for Ashet OS specific file formats", "version": "0.0.1", "engines": { - "vscode": "^1.100.0" + "vscode": "^1.91.0" }, "categories": [ "Programming Languages" @@ -34,4 +34,4 @@ "dependencies": { "@vscode/vsce": "^3.5.0" } -} +} \ No newline at end of file