From 0f1f427f00d9a75a9cdcff2e6a27380e2e183285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 4 Feb 2026 13:36:48 +0100 Subject: [PATCH 01/42] Defines the much refined date/time kernel interface --- src/abi/src/ashet.abi | 210 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 26 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index bb7f0261..ead2f029 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -361,21 +361,188 @@ namespace clock { } } +/// This namespace groups functions related to wall clock and calendar operations. +/// +/// 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. 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 in the UTC time zone. + /// + /// NOTE: This type isn't a monotonic type but has holes inside. + /// It is still organized in a way that it's trivially sortable by + /// comparison as a signed integer. + 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: Precision of timing depends on the current hardware, + /// but should always be at least in seconds precision. syscall now { - out datetime: DateTime; + /// The current date and time of the system. + out dt: DateTime; + } + + /// Updates the systems current date/time value. + syscall set { + /// The new date and time of the system. + in dt: DateTime; + + /// The DateTime value does not encode a valid number of seconds. + error InvalidValue; } - /// Sleeps until `datetime.now()` returns a point in time that comes after `when`. + /// Sleeps until `datetime.now()` returns a point in time that comes after `when`. async_call Alarm { /// Earliest possible date time of when the alarm triggers. in when: DateTime; } + //? + //? Timezone Management + //? + + /// Sets the current local time zone offset to UTC. + syscall set_timezone_offset + { + /// The offset of the local time to UTC in minutes. + /// + /// RANGE: -1440 - 1440 + in minutes: i16; + + /// The provided 'minutes' offset was not in the legal range. + error OutOfRange; + } + + /// Gets the current local time zone offset to UTC. + syscall get_timezone_offset + { + /// The offset of the local time to UTC in minutes. + /// + /// RANGE: -1440 - 1440 + out minutes: i16; + } + + /// Loads a "timezone data" file according to the [tz database](https://en.wikipedia.org/wiki/Tz_database). + /// + /// 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 blob of the timezone data file. + 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; + } + + //? + //? 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 encodes dates prior to 1582. + /// + /// LORE: This structure isn't a necessity for the kernel API, but is introduces + /// 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 { + /// RANGE: -32768 - 32767 + field year: i16; + + /// RANGE: 1-12 + field month: u8; + + /// RANGE: 1-31 + field day: u8; + + /// RANGE: 0-23 + field hour: u8; + + /// RANGE: 0-59 + field minute: u8; + + /// RANGE: 0-59 + field second: u8; + + /// RANGE: 0-999 + field millis: 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; + + /// The offset to UTC in minutes for the date. + /// NOTE: Use 0 for UTC. + 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 OutOfRange; + } + + /// 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; + + /// The offset to UTC in minutes for the date. + /// NOTE: Use 0 for UTC. + in local_offset: i16; + + /// The resulting gregorian date. + out gregorian: GregorianDate; + + /// The date/time value contains an invalid value. + error InvalidValue; + + /// The local time zone offset is not in the legal range. + error OutOfRange; + } } namespace video { @@ -1080,9 +1247,9 @@ namespace draw { } /// 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 + /// and will return the height of all lines and the width of /// the longest line. syscall measure_text_size { in font: Font; @@ -1574,15 +1741,6 @@ struct UUID { field bytes: [16]u8; } - -/// 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; - ... -} - /// Time in nanoseconds since system startup. enum Absolute : u64 { item system_start = 0; @@ -2795,7 +2953,7 @@ struct MessageBoxEvent { /// /// 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. /// @@ -2816,26 +2974,26 @@ struct MessageBoxEvent { /// 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 +/// 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 }; @@ -2851,7 +3009,7 @@ bitstruct Color : u8 { /// 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. + /// 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. @@ -3232,8 +3390,8 @@ struct FileInfo { /// The size of the file in bytes. field size: u64; field attributes: FileAttributes; - field creation_date: DateTime; - field modified_date: DateTime; + field creation_date: datetime.DateTime; + field modified_date: datetime.DateTime; } From 3b3c1b6e6d4b29bc667b2c6ebd0c03c5d4c014fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 4 Feb 2026 20:52:44 +0100 Subject: [PATCH 02/42] Greatly improves the date/time api --- src/abi/src/ashet.abi | 230 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 208 insertions(+), 22 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index ead2f029..1885dd3a 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -354,7 +354,7 @@ namespace clock { out time: Absolute; } - /// Sleeps until `clock.monotonic()` returns at least `timeout`. + /// Returns when `clock.monotonic()` is at least `timeout`. async_call Timer { /// Monotonic timestamp in nanoseconds until the operation completes. in timeout: Absolute; @@ -363,11 +363,47 @@ namespace clock { /// This namespace groups functions 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 { //? //? Basic date/time management and query @@ -376,11 +412,20 @@ namespace datetime { /// Encodes a packed structure that encodes a calendar date + wall clock time /// into a single integer. /// - /// NOTE: A DateTime value is always in the UTC time zone. + /// 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`. /// - /// NOTE: This type isn't a monotonic type but has holes inside. - /// It is still organized in a way that it's trivially sortable by - /// comparison as a signed integer. + /// 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. /// @@ -395,7 +440,7 @@ namespace datetime { /// /// RANGE: 2000-01-01 = 0 /// - /// LORE: The epoch was chosen to be the first january of 2000 as for + /// 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 @@ -405,51 +450,72 @@ namespace datetime { /// 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 { /// The current date and time of the system. out dt: DateTime; } - /// Updates the systems current date/time value. + /// 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; - /// The DateTime value does not encode a valid number of seconds. + /// 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()` is bigger than `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 //? - /// Sets the current local time zone offset to UTC. - syscall set_timezone_offset + /// 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 - in minutes: i16; - - /// The provided 'minutes' offset was not in the legal range. - error OutOfRange; + /// + /// 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; } - /// Gets the current local time zone offset to UTC. - syscall get_timezone_offset + /// 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 - out minutes: i16; + in minutes: i16; + + /// The provided 'minutes' offset was not in the legal range. + error InvalidZoneOffset; } /// Loads a "timezone data" file according to the [tz database](https://en.wikipedia.org/wiki/Tz_database). @@ -462,7 +528,12 @@ namespace datetime { /// in a good way. syscall load_timezone_data { - /// The binary blob of the timezone data file. + /// 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. @@ -472,18 +543,37 @@ namespace datetime { error InvalidData; } + /// Queries the timezone offset for a given point in time. + /// + /// 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; + } + //? //? 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 encodes dates prior to 1582. + /// which means it can encode dates prior to 1582. /// - /// LORE: This structure isn't a necessity for the kernel API, but is introduces + /// 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; @@ -500,6 +590,7 @@ namespace datetime { field minute: u8; /// RANGE: 0-59 + /// NOTE: 60 is *not allowed*. See the note on `datetime` for more information. field second: u8; /// RANGE: 0-999 @@ -513,6 +604,7 @@ namespace datetime { /// 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`. @@ -522,7 +614,7 @@ namespace datetime { error InvalidValue; /// The local time zone offset is not in the legal range. - error OutOfRange; + error InvalidZoneOffset; } /// Converts a DateTime value into a gregorian date. @@ -532,6 +624,7 @@ namespace datetime { /// The offset to UTC in minutes for the date. /// NOTE: Use 0 for UTC. + /// RANGE: -1440 - 1440 in local_offset: i16; /// The resulting gregorian date. @@ -541,8 +634,101 @@ namespace datetime { error InvalidValue; /// The local time zone offset is not in the legal range. + error InvalidZoneOffset; + + /// `dt` points to a year not representable by `GregorianDate`. error OutOfRange; } + + /// 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; + } + + /// 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; + + /// 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; + + /// The date/time value representing the given date. + out dt: DateTime; + + /// The gregorian date contains an invalidly specified date. + error InvalidValue; + + /// 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. + field reject = 0; + + /// The time is adjusted to the first possible past point in time. + /// EXAMPLE: `02:30:00.000` is mapped to `01:59:59.999`. + field past = 1; + + /// The time is adjusted to the first possible future point in time. + /// EXAMPLE: `02:30:00.000` is mapped to `03:00:00.000`. + field future = 2; + + /// 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`. + field closer = 3; + } + + /// 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. + field reject = 0; + + /// If the time is ambiguous, assume the earlier variant. + /// EXAMPLE: `02:30` is 2.5 hours past midnight. + field earlier = 1; + + /// If the time is ambiguous, assume the later variant. + /// EXAMPLE: `02:30` is 3.5 hours past midnight. + field later = 2; + } } namespace video { From debaee151b59c80445f3e5e563004ebd571e05f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 4 Feb 2026 21:28:49 +0100 Subject: [PATCH 03/42] Refactors clock namespace and adjusts some more nitpicks --- src/abi/src/ashet.abi | 63 +++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 1885dd3a..58523b0b 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -46,7 +46,7 @@ namespace resources { in @"resource": SystemResource; } - /// Immediatly destroys the resource and releases its memory. + /// immediately destroys the resource and releases its memory. /// /// NOTE: This will *always* destroy the resource, even if it's /// also owned by another process. @@ -347,21 +347,42 @@ namespace process { } } +/// 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 { + /// Time in nanoseconds since system startup. + enum Absolute : u64 { + item system_start = 0; + ... + } + + /// 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; } - /// Returns when `clock.monotonic()` is 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 groups functions related to wall clock and calendar operations. +/// 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. @@ -478,7 +499,7 @@ namespace datetime { error InvalidValue; } - /// Completes when `datetime.now()` is bigger than `when`. + /// Completes when `datetime.now() >= when`. /// /// NOTE: A call to `set` may trigger all active alarm calls that /// are now satisfied. @@ -698,36 +719,36 @@ namespace datetime { /// 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. - field reject = 0; + item reject = 0; /// The time is adjusted to the first possible past point in time. /// EXAMPLE: `02:30:00.000` is mapped to `01:59:59.999`. - field past = 1; + item past = 1; /// The time is adjusted to the first possible future point in time. /// EXAMPLE: `02:30:00.000` is mapped to `03:00:00.000`. - field future = 2; + item future = 2; /// 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`. - field closer = 3; + item closer = 3; } /// 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. - field reject = 0; + item reject = 0; /// If the time is ambiguous, assume the earlier variant. /// EXAMPLE: `02:30` is 2.5 hours past midnight. - field earlier = 1; + item earlier = 1; /// If the time is ambiguous, assume the later variant. /// EXAMPLE: `02:30` is 3.5 hours past midnight. - field later = 2; + item later = 2; } } @@ -1927,18 +1948,8 @@ struct UUID { field bytes: [16]u8; } -/// Time in nanoseconds since system startup. -enum Absolute : u64 { - item system_start = 0; - ... -} - -/// A duration in nanoseconds. -enum Duration : u64 { ... } - - enum PipeMode : u8 { - /// Completes immediatly even if no elements could be processed. + /// Completes immediately even if no elements could be processed. item nonblocking = 0; /// Returns when at least one element could be processed. item at_least_one = 1; @@ -3773,7 +3784,7 @@ namespace io { async_call break { in port: SerialPort; - in duration: Duration; + in duration: clock.Duration; error InvalidHandle; From cb148853324ae644148dddeb87c12a48dcba649f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 4 Feb 2026 22:01:54 +0100 Subject: [PATCH 04/42] Moves a lot of types around in the ABI, cleans up polluted global namespace. --- src/abi/src/ashet.abi | 3579 +++++++++++++++++++++-------------------- 1 file changed, 1800 insertions(+), 1779 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 58523b0b..68730181 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -12,6 +12,7 @@ enum SystemResource : usize /// All syscalls related to generic resource management. namespace resources { + //? TODO: Introduce the concept of strong/weak resources. /// Returns the type of the system resource. syscall get_type { @@ -23,7 +24,7 @@ namespace resources { /// Returns the current owner of this resource. syscall get_owners { in @"resource": SystemResource; - in owners: ?[]Process; + in owners: ?[]process.Process; out count: usize; } @@ -31,7 +32,7 @@ namespace resources { /// can safely access it without fear of having a use-after-free. syscall send_to_process { in @"resource": SystemResource; - in target: Process; + in target: process.Process; error DeadProcess; error InvalidHandle; error SystemResources; @@ -56,6 +57,40 @@ namespace resources { } namespace overlapped { + struct Await_Options { + field wait: Wait; + field thread_affinity: Thread_Affinity; + + enum Thread_Affinity : u8 { + /// Waits for ARCs scheduled from *any* thread in the current process. + item all_threads; + + /// Waits for ARCs scheduled from *this* thread. + item this_thread; + } + + enum Wait : u8 { + /// Don't wait for any additional calls to complete, just return + /// whatever was completed in the meantime. + 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; + + } + } + /// Handle to an asynchronously running (system) call. struct ARC @@ -143,6 +178,9 @@ namespace overlapped { /// Syscalls related to processes namespace process { + resource Process { } + + resource Thread { } enum ExitCode : u32 { item success = 0; @@ -151,6 +189,26 @@ namespace process { item killed = 0xFFFFFFFF; } + struct SpawnProcessArg { + field type: Type; + field value: Value; + + enum Type : u8 { + item string = 0; + item @"resource" = 1; + } + + union Value { + field text: String; + field @"resource": SystemResource; + } + + struct String { + field text: str; + } + } + + /// Returns a pointer to the file name of the process. syscall get_file_name { in target: ?Process; @@ -189,7 +247,7 @@ namespace process { /// Spawns a new process 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. @@ -249,6 +307,14 @@ namespace process { } namespace debug { + enum LogLevel : u8 { + item critical = 0; + item err = 1; + item warn = 2; + item notice = 3; + item debug = 4; + } + /// Writes to the system debug log. syscall write_log { in log_level: LogLevel; @@ -753,6 +819,34 @@ namespace datetime { } namespace video { + resource VideoOutput { } + + /// Index of the systems video outputs. + enum VideoOutputID : u8 { + /// The primary video output + item primary = 0; + ... + } + + 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; + + /// Length of a scanline. + field stride: usize; + + /// Number of valid elements in a scanline + field width: u16; + + /// Number of valid scanlines. + field height: u16; + } + /// Returns a list of all video outputs. /// /// If `ids` is `null`, the total number of available outputs is returned; @@ -816,12 +910,885 @@ namespace random { } namespace input { + union InputEvent { + field event_type: Type; + field mouse: MouseEvent; + field keyboard: KeyboardEvent; + + enum Type : u16 { + item key_press = 0; + item key_release = 1; + + item mouse_rel_motion = 2; + item mouse_abs_motion = 3; + item mouse_button_press = 4; + item mouse_button_release = 5; + } + } + + 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; + } + + + //? TODO: Implement the idea of "input devices" which can be queried + //? and waited for in batches (wait for one even from several + //? devices in an input group) + /// Waits for an input event and completes when any input was done. async_call GetEvent { out event: InputEvent; error InProgress; error NonExclusiveAccess; } + + + 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; + } + + /// + /// 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 `c` and `C` + /// NOTE: Typically remapped for other languages in the host system. + item c = 0x06; + + /// Keyboard `d` and `D` + item d = 0x07; + + /// Keyboard `e` and `E` + item e = 0x08; + + /// Keyboard `f` and `F` + item f = 0x09; + + /// Keyboard `g` and `G` + item g = 0x0A; + + /// Keyboard `h` and `H` + item h = 0x0B; + + /// Keyboard `i` and `I` + item i = 0x0C; + + /// Keyboard `j` and `J` + item j = 0x0D; + + /// Keyboard `k` and `K` + item k = 0x0E; + + /// Keyboard `l` and `L` + item l = 0x0F; + + /// Keyboard `m` and `M` + /// NOTE: Typically remapped for other languages in the host system. + item m = 0x10; + + /// 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 `p` and `P` + /// NOTE: Typically remapped for other languages in the host system. + item p = 0x13; + + /// Keyboard `q` and `Q` + /// NOTE: Typically remapped for other languages in the host system. + item q = 0x14; + + /// Keyboard `r` and `R` + item r = 0x15; + + /// Keyboard `s` and `S` + item s = 0x16; + + /// Keyboard `t` and `T` + item t = 0x17; + + /// Keyboard `u` and `U` + item u = 0x18; + + /// Keyboard `v` and `V` + item v = 0x19; + + /// Keyboard `w` and `W` + /// NOTE: Typically remapped for other languages in the host system. + item w = 0x1A; + + /// Keyboard `x` and `X` + /// NOTE: Typically remapped for other languages in the host system. + item x = 0x1B; + + /// Keyboard `y` and `Y` + /// NOTE: Typically remapped for other languages in the host system. + item y = 0x1C; + + /// Keyboard `z` and `Z` + /// NOTE: Typically remapped for other languages in the host system. + item z = 0x1D; + + + /// Keyboard `1` and `!` + /// NOTE: Typically remapped for other languages in the host system. + item @"1" = 0x1E; + + /// Keyboard `2` and `@` + /// NOTE: Typically remapped for other languages in the host system. + item @"2" = 0x1F; + + /// Keyboard `3` and `#` + /// NOTE: Typically remapped for other languages in the host system. + item @"3" = 0x20; + + /// Keyboard `4` and `$` + /// NOTE: Typically remapped for other languages in the host system. + item @"4" = 0x21; + + /// Keyboard `5` and `%` + /// NOTE: Typically remapped for other languages in the host system. + item @"5" = 0x22; + + /// Keyboard `6` and `∧` + /// NOTE: Typically remapped for other languages in the host system. + item @"6" = 0x23; + + /// Keyboard `7` and `&` + /// NOTE: Typically remapped for other languages in the host system. + item @"7" = 0x24; + + /// Keyboard `8` and `*` + /// NOTE: Typically remapped for other languages in the host system. + item @"8" = 0x25; + + /// Keyboard `9` and `(` + /// NOTE: Typically remapped for other languages in the host system. + item @"9" = 0x26; + + /// Keyboard `0` and `)` + /// NOTE: Typically remapped for other languages in the host system. + item @"0" = 0x27; + + /// Keyboard Return (ENTER) + item enter = 0x28; + + /// 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 Tab + item tab = 0x2B; + + /// Keyboard Spacebar + item space = 0x2C; + + /// Keyboard `-` and `_` + item minus = 0x2D; + + /// Keyboard `=` and `+` + /// NOTE: Typically remapped for other languages in the host system. + item equals = 0x2E; + + /// Keyboard `[` and `{` + /// NOTE: Typically remapped for other languages in the host system. + item square_bracket_open = 0x2F; + + /// Keyboard `]` and `}` + /// NOTE: Typically remapped for other languages in the host system. + item square_bracket_close = 0x30; + + /// Keyboard `\\` and `|` + /// NOTE: Typically remapped for other languages in the host system. + item backslash = 0x31; + + /// 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 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; + + /// Keypad `8` and Up Arrow + item kp_8 = 0x60; + + /// Keypad `9` and PageUp + item kp_9 = 0x61; + + /// Keypad `0` and Insert + item kp_0 = 0x62; + + /// Keypad `.` and Delete + item kp_period = 0x63; + + /// 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 Application + /// NOTE: Windows key for Windows 95, and Compose. + item application = 0x65; + + /// Keyboard Power + item power = 0x66; + + /// Keypad `=` + item kp_equals = 0x67; + + /// Keyboard F13 + item f13 = 0x68; + + /// Keyboard F14 + item f14 = 0x69; + + /// Keyboard F15 + item f15 = 0x6A; + + /// Keyboard F16 + item f16 = 0x6B; + + /// Keyboard F17 + item f17 = 0x6C; + + /// Keyboard F18 + item f18 = 0x6D; + + /// Keyboard F19 + item f19 = 0x6E; + + /// Keyboard F20 + item f20 = 0x6F; + + /// Keyboard F21 + item f21 = 0x70; + + /// Keyboard F22 + item f22 = 0x71; + + /// Keyboard F23 + item f23 = 0x72; + + /// Keyboard F24 + item f24 = 0x73; + + /// Keyboard Execute + item execute = 0x74; + + /// Keyboard Help + item help = 0x75; + + /// Keyboard Menu + item menu = 0x76; + + /// Keyboard Select + item select = 0x77; + + /// Keyboard Stop + item stop = 0x78; + + /// Keyboard Again + item again = 0x79; + + /// Keyboard Undo + item undo = 0x7A; + + /// Keyboard Cut + item cut = 0x7B; + + /// Keyboard Copy + item copy = 0x7C; + + /// Keyboard Paste + item paste = 0x7D; + + /// Keyboard Find + item find = 0x7E; + + /// Keyboard Mute + item mute = 0x7F; + + /// Keyboard Volume Up + item volume_up = 0x80; + + /// Keyboard Volume Down + item volume_down = 0x81; + + /// 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 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 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; + + /// 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; + + /// Keypad Equal Sign + /// NOTE: Used on AS/400 keyboards. + item kp_equals_as400 = 0x86; + + + /// 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 International2 + item international2 = 0x88; + + /// Keyboard International3 + item international3 = 0x89; + + /// Keyboard International4 + item international4 = 0x8A; + + /// Keyboard International5 + item international5 = 0x8B; + + /// 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 International9 + /// NOTE: Undefined, available for other Front End Language Processors. + item international9 = 0x8F; + + /// 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 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 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 LANG5 + /// NOTE: Defines the Zenkaku/Hankaku key for Japanese USB word-processing keyboards. + item lang5 = 0x94; + + /// Keyboard LANG6 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang6 = 0x95; + + /// Keyboard LANG7 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang7 = 0x96; + + /// Keyboard LANG8 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang8 = 0x97; + + /// Keyboard LANG9 + /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. + item lang9 = 0x98; + + + + /// Keyboard Alternate Erase + /// NOTE: Example, Erase-Eaze™ key. + item alternate_erase = 0x99; + + /// 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 Cancel + item term_cancel = 0x9B; + + /// Keyboard Clear + item term_clear = 0x9C; + + /// Keyboard Prior + item term_prior = 0x9D; + + /// Keyboard Return + item term_return = 0x9E; + + /// Keyboard Separator + item term_separator = 0x9F; + + /// Keyboard Out + item term_out = 0xA0; + + /// Keyboard Oper + item term_oper = 0xA1; + + /// Keyboard Clear/Again + item term_clear_again = 0xA2; + + /// Keyboard CrSel/Props + item term_crsel_props = 0xA3; + + /// Keyboard ExSel + item term_exsel = 0xA4; + + /// Keypad `00` + item kp_double_0 = 0xB0; + + /// Keypad `000` + item kp_triple_0 = 0xB1; + + /// 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; + + /// 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; + + /// 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; + + /// 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 `(` + item kp_round_bracket_open = 0xB6; + + /// Keypad `)` + item kp_round_bracket_close = 0xB7; + + /// Keypad `{` + item kp_curly_bracket_open = 0xB8; + + /// Keypad `}` + item kp_curly_bracket_close = 0xB9; + + /// Keypad Tab + item kp_tab = 0xBA; + + /// Keypad Backspace + item kp_backspace = 0xBB; + + /// Keypad `A` + item kp_a = 0xBC; + + /// Keypad `B` + item kp_b = 0xBD; + + /// Keypad `C` + item kp_c = 0xBE; + + /// Keypad `D` + item kp_d = 0xBF; + + /// Keypad `E` + item kp_e = 0xC0; + + /// Keypad `F` + item kp_f = 0xC1; + + /// Keypad XOR + item kp_logic_xor = 0xC2; + + /// Keypad `∧` + item kp_logic_and = 0xC3; + + /// Keypad % + item kp_percent = 0xC4; + + /// Keypad `<` + item kp_less_than = 0xC5; + + /// Keypad `>` + item kp_greater_than = 0xC6; + + /// Keypad `&` + item kp_ampersand = 0xC7; + + /// Keypad `&&` + item kp_double_ampersand = 0xC8; + + /// Keypad `|` + item kp_pipe = 0xC9; + + /// Keypad `||` + item kp_double_pipe = 0xCA; + + /// Keypad `:` + item kp_colon = 0xCB; + + /// Keypad `#` + item kp_hash = 0xCC; + + /// Keypad Space + item kp_space = 0xCD; + + /// Keypad `@` + item kp_at = 0xCE; + + /// Keypad `!` + item kp_exlamation = 0xCF; + + /// Keypad Memory Store + item kp_memory_store = 0xD0; + + /// Keypad Memory Recall + item kp_memory_recall = 0xD1; + + /// Keypad Memory Clear + item kp_memory_clear = 0xD2; + + /// Keypad Memory Add + item kp_memory_add = 0xD3; + + /// Keypad Memory Subtract + item kp_memory_subtract = 0xD4; + + /// Keypad Memory Multiply + item kp_memory_multiply = 0xD5; + + /// Keypad Memory Divide + item kp_memory_divide = 0xD6; + + /// Keypad `+/-` + item kp_plus_minus = 0xD7; + + /// 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; + + ... + } } namespace network { @@ -859,6 +1826,8 @@ namespace network { } namespace udp { + resource UdpSocket { } + /// Creates a new UDP socket. syscall create_socket { out socket: UdpSocket; @@ -954,6 +1923,8 @@ namespace network { } namespace tcp { + resource TcpSocket { } + /// Creates a new TCP socket. syscall create_socket { out socket: TcpSocket; @@ -1063,34 +2034,142 @@ namespace network { /// /// 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 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 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. + /// 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; + + 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. + ... } - /// Flushes all open files to disk. - async_call Sync { - error DiskError; + bitstruct FileAttributes : u16 { + field directory: bool; + reserve u15 = 0; } - /// 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; + enum FileAccess : u8 { + item read_only = 0; + item write_only = 1; + item read_write = 2; } - /// opens a directory on a filesystem - async_call OpenDrive { - in fs_id: FileSystemId; - in path: str; - out dir: Directory; - error DiskError; + 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; + } + + 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 + + /// string identifier of a file system driver ('FAT32', ...) + field filesystem: [32]u8; //? TODO: USe max_fs_type_len instead of 32 + + bitstruct Flags : u16 { + /// is the system boot disk + field system: bool; + + /// the file system can be removed by the user + field removable: bool; + + /// the file system is mounted as read-only + field read_only: bool; + + reserve u13 = 0; + } + } + + 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.DateTime; + field modified_date: datetime.DateTime; + } + + + resource File { } + + resource Directory { } + + //? TODO: Implement the idea of a "file system location", a type that binds a directory + relative path together + + /// Finds a file system by name + syscall find_filesystem { + in name: str; + out id: FileSystemId; + } + + /// Flushes all open files to disk. + async_call Sync { + error DiskError; + } + + /// 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; + } + + /// 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; @@ -1295,6 +2374,8 @@ namespace fs { } namespace shm { + resource SharedMemory { } + /// 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 { @@ -1316,10 +2397,21 @@ namespace shm { out pointer: [*]align(16) u8; error InvalidHandle; } - } namespace pipe { + + resource Pipe { } + + enum PipeMode : u8 { + /// Completes immediately 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; + } + /// 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 @@ -1378,6 +2470,10 @@ namespace pipe { } namespace sync { + resource SyncEvent { } + + resource Mutex { } + /// Creates a new `SyncEvent` object that can be used to synchronize /// different processes. syscall create_event { @@ -1430,6 +2526,34 @@ namespace sync { } namespace draw { + resource Font { } + + enum FontType : u32 { + item bitmap = 0; + item vector = 1; + ... + } + + /// 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 data for the given font name, if any. syscall get_system_font { in font_name: str; @@ -1475,7 +2599,7 @@ namespace draw { /// 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; + in output: video.VideoOutput; out handle: Framebuffer; error InvalidHandle; error SystemResources; @@ -1483,7 +2607,7 @@ namespace draw { /// Creates a new framebuffer that allows painting into a GUI window. syscall create_window_framebuffer { - in window: Window; + in window: gui.Window; out handle: Framebuffer; error InvalidHandle; error SystemResources; @@ -1491,7 +2615,7 @@ namespace draw { /// Creates a new framebuffer that allows painting into a widget. syscall create_widget_framebuffer { - in widget: Widget; + in widget: gui.Widget; out handle: Framebuffer; error InvalidHandle; error SystemResources; @@ -1515,7 +2639,7 @@ namespace draw { /// Other framebuffer types are not allowed to be passed. syscall get_framebuffer_memory { in fb: Framebuffer; - out memory: VideoMemory; + out memory: video.VideoMemory; error InvalidHandle; error Unsupported; } @@ -1550,6 +2674,440 @@ namespace draw { } 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; @@ -1557,6 +3115,9 @@ namespace gui { error SystemResources; } + + + /// Opens a message box popup window and prompts the user for response. async_call ShowMessageBox { in desktop: Desktop; @@ -1835,6 +3396,8 @@ namespace gui { } namespace service { + resource 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 { @@ -1864,7 +3427,7 @@ namespace service { /// Returns the process that created this service. syscall get_process { in svc: Service; - out process: Process; + out process: process.Process; error InvalidHandle; } @@ -1877,1763 +3440,48 @@ namespace service { } } -resource Service { } - -resource SharedMemory { } - -resource Pipe { } +/// +/// The I/O namespace contains APIs to interface with external hardware like serial ports, I²C busses and so on. +/// +namespace io { -resource Process { } + /// + /// Functions and types related to serial busses like RS232, RS485 or similar. + /// + namespace serial { + enum SerialPortID : u32 { + ... + } -resource Thread { } + //? syscall enumerate { + //? in list: []SerialPortID; -resource TcpSocket { } + //? out count: usize; + //? } -resource UdpSocket { } + //? /// Queries information about the given serial port id. + //? syscall query_metadata { + //? in id: SerialPortID; + //? in name_buf: ?[]u8; -resource File { } + //? out name_len: usize; -resource Directory { } + //? /// The given id does not exist. + //? error NotFound; + //? } -resource VideoOutput { } + resource SerialPort { } -resource Font { } + //? syscall open { + //? in id: SerialPortID; + //? out port: SerialPort; -/// A framebuffer is something that can be drawn on. -resource Framebuffer { } + //? /// The given id does not exist. + //? error NotFound; -resource Window { } - -resource Widget { } - -resource Desktop { } - -resource WidgetType { } - -resource SyncEvent { } - -resource Mutex { } - -/// 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 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. -/// 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; - -struct UUID { - field bytes: [16]u8; -} - -enum PipeMode : u8 { - /// Completes immediately 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; -} - -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; - - ... -} - - -struct Await_Options { - field wait: Wait; - field thread_affinity: Thread_Affinity; - - enum Thread_Affinity : u8 { - /// Waits for ARCs scheduled from *any* thread in the current process. - item all_threads; - - /// Waits for ARCs scheduled from *this* thread. - item this_thread; - } - - enum Wait : u8 { - /// Don't wait for any additional calls to complete, just return - /// whatever was completed in the meantime. - 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; - - } -} - -/// 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. - ... -} - -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 `c` and `C` - /// NOTE: Typically remapped for other languages in the host system. - item c = 0x06; - - /// Keyboard `d` and `D` - item d = 0x07; - - /// Keyboard `e` and `E` - item e = 0x08; - - /// Keyboard `f` and `F` - item f = 0x09; - - /// Keyboard `g` and `G` - item g = 0x0A; - - /// Keyboard `h` and `H` - item h = 0x0B; - - /// Keyboard `i` and `I` - item i = 0x0C; - - /// Keyboard `j` and `J` - item j = 0x0D; - - /// Keyboard `k` and `K` - item k = 0x0E; - - /// Keyboard `l` and `L` - item l = 0x0F; - - /// Keyboard `m` and `M` - /// NOTE: Typically remapped for other languages in the host system. - item m = 0x10; - - /// 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 `p` and `P` - /// NOTE: Typically remapped for other languages in the host system. - item p = 0x13; - - /// Keyboard `q` and `Q` - /// NOTE: Typically remapped for other languages in the host system. - item q = 0x14; - - /// Keyboard `r` and `R` - item r = 0x15; - - /// Keyboard `s` and `S` - item s = 0x16; - - /// Keyboard `t` and `T` - item t = 0x17; - - /// Keyboard `u` and `U` - item u = 0x18; - - /// Keyboard `v` and `V` - item v = 0x19; - - /// Keyboard `w` and `W` - /// NOTE: Typically remapped for other languages in the host system. - item w = 0x1A; - - /// Keyboard `x` and `X` - /// NOTE: Typically remapped for other languages in the host system. - item x = 0x1B; - - /// Keyboard `y` and `Y` - /// NOTE: Typically remapped for other languages in the host system. - item y = 0x1C; - - /// Keyboard `z` and `Z` - /// NOTE: Typically remapped for other languages in the host system. - item z = 0x1D; - - - /// Keyboard `1` and `!` - /// NOTE: Typically remapped for other languages in the host system. - item @"1" = 0x1E; - - /// Keyboard `2` and `@` - /// NOTE: Typically remapped for other languages in the host system. - item @"2" = 0x1F; - - /// Keyboard `3` and `#` - /// NOTE: Typically remapped for other languages in the host system. - item @"3" = 0x20; - - /// Keyboard `4` and `$` - /// NOTE: Typically remapped for other languages in the host system. - item @"4" = 0x21; - - /// Keyboard `5` and `%` - /// NOTE: Typically remapped for other languages in the host system. - item @"5" = 0x22; - - /// Keyboard `6` and `∧` - /// NOTE: Typically remapped for other languages in the host system. - item @"6" = 0x23; - - /// Keyboard `7` and `&` - /// NOTE: Typically remapped for other languages in the host system. - item @"7" = 0x24; - - /// Keyboard `8` and `*` - /// NOTE: Typically remapped for other languages in the host system. - item @"8" = 0x25; - - /// Keyboard `9` and `(` - /// NOTE: Typically remapped for other languages in the host system. - item @"9" = 0x26; - - /// Keyboard `0` and `)` - /// NOTE: Typically remapped for other languages in the host system. - item @"0" = 0x27; - - /// Keyboard Return (ENTER) - item enter = 0x28; - - /// 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 Tab - item tab = 0x2B; - - /// Keyboard Spacebar - item space = 0x2C; - - /// Keyboard `-` and `_` - item minus = 0x2D; - - /// Keyboard `=` and `+` - /// NOTE: Typically remapped for other languages in the host system. - item equals = 0x2E; - - /// Keyboard `[` and `{` - /// NOTE: Typically remapped for other languages in the host system. - item square_bracket_open = 0x2F; - - /// Keyboard `]` and `}` - /// NOTE: Typically remapped for other languages in the host system. - item square_bracket_close = 0x30; - - /// Keyboard `\\` and `|` - /// NOTE: Typically remapped for other languages in the host system. - item backslash = 0x31; - - /// 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 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; - - /// Keypad `8` and Up Arrow - item kp_8 = 0x60; - - /// Keypad `9` and PageUp - item kp_9 = 0x61; - - /// Keypad `0` and Insert - item kp_0 = 0x62; - - /// Keypad `.` and Delete - item kp_period = 0x63; - - /// 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 Application - /// NOTE: Windows key for Windows 95, and Compose. - item application = 0x65; - - /// Keyboard Power - item power = 0x66; - - /// Keypad `=` - item kp_equals = 0x67; - - /// Keyboard F13 - item f13 = 0x68; - - /// Keyboard F14 - item f14 = 0x69; - - /// Keyboard F15 - item f15 = 0x6A; - - /// Keyboard F16 - item f16 = 0x6B; - - /// Keyboard F17 - item f17 = 0x6C; - - /// Keyboard F18 - item f18 = 0x6D; - - /// Keyboard F19 - item f19 = 0x6E; - - /// Keyboard F20 - item f20 = 0x6F; - - /// Keyboard F21 - item f21 = 0x70; - - /// Keyboard F22 - item f22 = 0x71; - - /// Keyboard F23 - item f23 = 0x72; - - /// Keyboard F24 - item f24 = 0x73; - - /// Keyboard Execute - item execute = 0x74; - - /// Keyboard Help - item help = 0x75; - - /// Keyboard Menu - item menu = 0x76; - - /// Keyboard Select - item select = 0x77; - - /// Keyboard Stop - item stop = 0x78; - - /// Keyboard Again - item again = 0x79; - - /// Keyboard Undo - item undo = 0x7A; - - /// Keyboard Cut - item cut = 0x7B; - - /// Keyboard Copy - item copy = 0x7C; - - /// Keyboard Paste - item paste = 0x7D; - - /// Keyboard Find - item find = 0x7E; - - /// Keyboard Mute - item mute = 0x7F; - - /// Keyboard Volume Up - item volume_up = 0x80; - - /// Keyboard Volume Down - item volume_down = 0x81; - - /// 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 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 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; - - /// 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; - - /// Keypad Equal Sign - /// NOTE: Used on AS/400 keyboards. - item kp_equals_as400 = 0x86; - - - /// 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 International2 - item international2 = 0x88; - - /// Keyboard International3 - item international3 = 0x89; - - /// Keyboard International4 - item international4 = 0x8A; - - /// Keyboard International5 - item international5 = 0x8B; - - /// 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 International9 - /// NOTE: Undefined, available for other Front End Language Processors. - item international9 = 0x8F; - - /// 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 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 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 LANG5 - /// NOTE: Defines the Zenkaku/Hankaku key for Japanese USB word-processing keyboards. - item lang5 = 0x94; - - /// Keyboard LANG6 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang6 = 0x95; - - /// Keyboard LANG7 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang7 = 0x96; - - /// Keyboard LANG8 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang8 = 0x97; - - /// Keyboard LANG9 - /// NOTE: Reserved for language-specific functions, such as Front End Processors and Input Method Editors. - item lang9 = 0x98; - - - - /// Keyboard Alternate Erase - /// NOTE: Example, Erase-Eaze™ key. - item alternate_erase = 0x99; - - /// 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 Cancel - item term_cancel = 0x9B; - - /// Keyboard Clear - item term_clear = 0x9C; - - /// Keyboard Prior - item term_prior = 0x9D; - - /// Keyboard Return - item term_return = 0x9E; - - /// Keyboard Separator - item term_separator = 0x9F; - - /// Keyboard Out - item term_out = 0xA0; - - /// Keyboard Oper - item term_oper = 0xA1; - - /// Keyboard Clear/Again - item term_clear_again = 0xA2; - - /// Keyboard CrSel/Props - item term_crsel_props = 0xA3; - - /// Keyboard ExSel - item term_exsel = 0xA4; - - /// Keypad `00` - item kp_double_0 = 0xB0; - - /// Keypad `000` - item kp_triple_0 = 0xB1; - - /// 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; - - /// 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; - - /// 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; - - /// 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 `(` - item kp_round_bracket_open = 0xB6; - - /// Keypad `)` - item kp_round_bracket_close = 0xB7; - - /// Keypad `{` - item kp_curly_bracket_open = 0xB8; - - /// Keypad `}` - item kp_curly_bracket_close = 0xB9; - - /// Keypad Tab - item kp_tab = 0xBA; - - /// Keypad Backspace - item kp_backspace = 0xBB; - - /// Keypad `A` - item kp_a = 0xBC; - - /// Keypad `B` - item kp_b = 0xBD; - - /// Keypad `C` - item kp_c = 0xBE; - - /// Keypad `D` - item kp_d = 0xBF; - - /// Keypad `E` - item kp_e = 0xC0; - - /// Keypad `F` - item kp_f = 0xC1; - - /// Keypad XOR - item kp_logic_xor = 0xC2; - - /// Keypad `∧` - item kp_logic_and = 0xC3; - - /// Keypad % - item kp_percent = 0xC4; - - /// Keypad `<` - item kp_less_than = 0xC5; - - /// Keypad `>` - item kp_greater_than = 0xC6; - - /// Keypad `&` - item kp_ampersand = 0xC7; - - /// Keypad `&&` - item kp_double_ampersand = 0xC8; - - /// Keypad `|` - item kp_pipe = 0xC9; - - /// Keypad `||` - item kp_double_pipe = 0xCA; - - /// Keypad `:` - item kp_colon = 0xCB; - - /// Keypad `#` - item kp_hash = 0xCC; - - /// Keypad Space - item kp_space = 0xCD; - - /// Keypad `@` - item kp_at = 0xCE; - - /// Keypad `!` - item kp_exlamation = 0xCF; - - /// Keypad Memory Store - item kp_memory_store = 0xD0; - - /// Keypad Memory Recall - item kp_memory_recall = 0xD1; - - /// Keypad Memory Clear - item kp_memory_clear = 0xD2; - - /// Keypad Memory Add - item kp_memory_add = 0xD3; - - /// Keypad Memory Subtract - item kp_memory_subtract = 0xD4; - - /// Keypad Memory Multiply - item kp_memory_multiply = 0xD5; - - /// Keypad Memory Divide - item kp_memory_divide = 0xD6; - - /// Keypad `+/-` - item kp_plus_minus = 0xD7; - - /// 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; - - ... -} - -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; - } - - union Value { - field text: String; - field @"resource": SystemResource; - } - - struct String { - field text: str; - } -} - -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 { ... } -} - -/// -/// 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 { ... } -} - -union InputEvent { - field event_type: Type; - field mouse: MouseEvent; - field keyboard: KeyboardEvent; - - enum Type : u16 { - item key_press = 0; - item key_release = 1; - - item mouse_rel_motion = 2; - item mouse_abs_motion = 3; - item mouse_button_press = 4; - item mouse_button_release = 5; - } -} - -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; - } -} - -/// 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! - - field x: i16; - field y: i16; - field dx: i16; - field dy: i16; - field button: MouseButton; -} - -struct KeyboardEvent { - field event_type: SharedEventType; //? MUST BE FIRST! - - /// The raw usage code for the key. Meaning depends on the layout; - /// kinda represents the physical position on the keyboard. - field usage: KeyUsageCode; - - /// 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 key in this event was pressed or released - field pressed: bool; - - /// The modifier keys currently active - field modifiers: KeyboardModifiers; -} - -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; -} - -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; -} - -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; - - /// Length of a scanline. - field stride: usize; - - /// Number of valid elements in a scanline - field width: u16; - - /// 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 - - /// string identifier of a file system driver ('FAT32', ...) - field filesystem: [32]u8; //? TODO: USe max_fs_type_len instead of 32 - - bitstruct Flags : u16 { - /// is the system boot disk - field system: bool; - - /// the file system can be removed by the user - field removable: bool; - - /// the file system is mounted as read-only - field read_only: bool; - - reserve u13 = 0; - } -} - -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.DateTime; - field modified_date: datetime.DateTime; -} - - -/// -/// The I/O namespace contains APIs to interface with external hardware like serial ports, I²C busses and so on. -/// -namespace io { - - /// - /// Functions and types related to serial busses like RS232, RS485 or similar. - /// - namespace serial { - enum SerialPortID : u32 { - ... - } - - //? 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; - - //? out name_len: usize; - - //? /// The given id does not exist. - //? error NotFound; - //? } - - resource SerialPort { } - - //? syscall open { - //? in id: SerialPortID; - //? out port: SerialPort; - - //? /// 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. @@ -4052,4 +3900,177 @@ 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; +} + +/// Event structures shared between different event groups +union SharedEventType { + field input: input.InputEvent.Type; + field widget: gui.WidgetEvent.Type; + field window: gui.WindowEvent.Type; +} + +struct MouseEvent { + field event_type: SharedEventType; //? MUST BE FIRST! + + field x: i16; + field y: i16; + field dx: i16; + field dy: i16; + field button: input.MouseButton; +} + +struct KeyboardEvent { + field event_type: SharedEventType; //? MUST BE FIRST! + + /// The raw usage code for the key. Meaning depends on the layout; + /// kinda represents the physical position on the keyboard. + field usage: input.KeyUsageCode; + + /// 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 key in this event was pressed or released + field pressed: bool; + + /// The modifier keys currently active + field modifiers: input.KeyboardModifiers; +} From 6da185755242819463979398d84c9c90836eb073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 5 Feb 2026 00:32:35 +0100 Subject: [PATCH 05/42] Remodels resource handling --- src/abi/src/ashet.abi | 283 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 256 insertions(+), 27 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 68730181..a73d8b92 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -11,9 +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 { - //? TODO: Introduce the concept of strong/weak resources. - /// Returns the type of the system resource. syscall get_type { in @"resource": SystemResource; @@ -21,38 +70,214 @@ namespace resources { error InvalidHandle; } - /// Returns the current owner of this resource. - syscall get_owners { - in @"resource": SystemResource; - in owners: ?[]process.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.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; } - /// immediately 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; } } @@ -376,15 +601,19 @@ namespace process { } namespace monitor { - /// Queries all owned resources by a process. + /// Queries all process resources. syscall enumerate_processes { + /// NOTE: The process handles will be bound to the calling process with the `at_least_weak` binding + /// to ensure access. in processes: ?[]Process; out count: usize; } - /// Queries all owned resources by a process. - syscall query_owned_resources { + /// Queries all bound resources by a process. + syscall query_bound_resources { in owner: Process; + /// NOTE: The resource handles will be bound to the calling process with the `at_least_weak` binding + /// to ensure access. in reslist: ?[]SystemResource; out count: usize; error InvalidHandle; From d4bf01ad7c58469e667641e1f54e375777b3d1e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 5 Feb 2026 13:40:20 +0100 Subject: [PATCH 06/42] Improves documentation and naming in the overlapped namespace. --- src/abi/src/ashet.abi | 263 ++++++++++++++++++++++++++++++------------ 1 file changed, 188 insertions(+), 75 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index a73d8b92..3fb8541e 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -269,7 +269,7 @@ namespace resources { /// /// Anchors can be used as groups for tether objects or as tokens passed /// between processes. - resource Anchor { } + resource Anchor { } /// Creates a new `Anchor` resource. syscall create_anchor { @@ -281,124 +281,237 @@ namespace resources { } } +/// 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.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: 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 destroyed, 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`. namespace overlapped { - struct Await_Options { - field wait: Wait; - field thread_affinity: Thread_Affinity; - - enum Thread_Affinity : u8 { - /// Waits for ARCs scheduled from *any* thread in the current process. - item all_threads; - - /// Waits for ARCs scheduled from *this* thread. - item this_thread; - } - - enum Wait : u8 { - /// Don't wait for any additional calls to complete, just return - /// whatever was completed in the meantime. - 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; - - } - } - - /// 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; + 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 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. /// - /// 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. + /// 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; - } + + /// Another `await_any_of` already awaits an event from `events`. + error InvalidOperation; - /// 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; + /// 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 From 3843873642eaf9cb16035046958dd0fab01dc992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 5 Feb 2026 23:55:58 +0100 Subject: [PATCH 07/42] Improves a lot of the process namespace --- src/abi/src/ashet.abi | 426 +++++++++++++++++++++++++++++++++++------- 1 file changed, 360 insertions(+), 66 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 3fb8541e..473720b6 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -514,26 +514,110 @@ namespace overlapped { } } -/// Syscalls related to processes +/// Syscalls related to processes, threads and execution control. namespace process { + /// 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 have zero associated threads, but still be active. This means it exists + /// and still may keep resources alive, but it cannot act anymore. + /// + /// 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 { } - enum ExitCode : u32 { - item success = 0; - item failure = 1; - ... - item killed = 0xFFFFFFFF; + /// 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; + } + + /// 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. + syscall kill { + /// The process that should be killed. + in target: Process; + + /// `target` is not a valid process resource. + error InvalidHandle; } - struct SpawnProcessArg { + + /// An argument passed to a process. + struct SpawnArg { + /// The associated name of the argument. + field name: str; + + /// The type of the argument. field type: Type; + + /// The associated value for `type`. field value: Value; enum Type : u8 { - item string = 0; - item @"resource" = 1; + /// 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 { @@ -546,60 +630,127 @@ namespace process { } } + /// Spawns a new process. + async_call Spawn { + /// Relative base directory for `path`. + in dir: fs.Directory; + + /// File name of the executable relative to `dir`. + in path: str; + + /// The arguments passed to the process. + /// It is safe to release the resource binding to the current process as soon as this operation returns. + /// + /// NOTE: The kernel will perform a copy of all strings inside `schedule`, so it is safe to + /// reuse the string memory after the schedule operation is performed. + /// + /// This prevents unwanted use-after-free by the kernel. + in arguments: []const SpawnArg; - /// Returns a pointer to the file name of the process. - syscall get_file_name { - in target: ?Process; - out file_name: str; - error InvalidHandle; + /// Handle to the spawned process. + out process: Process; + + error BadExecutable; + error DiskError; + error FileNotFound; + error InvalidHandle; + error InvalidPath; + error SystemResources; } - /// Returns the base address of the process. - syscall get_base_address { - in target: ?Process; - out base_address: usize; + 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 immediatly 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 value passed to `terminate.success` or `false` if + /// not terminated by `terminate`. + 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 queried. in target: ?Process; - in argv: ?[]SpawnProcessArg; - out count: usize; + + /// 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 a `at_least_weak` bind operation to ensure + /// resource access. + out argv: []const SpawnArg; + + /// `target` is not a valid process resource. error InvalidHandle; - } - /// Terminates the current process with the given exit code - syscall terminate { - in exit_code: ExitCode; - noreturn; + error SystemResources; } - /// Terminates a foreign process. - /// If the current process is passed, this function will not return - syscall kill { - in target: Process; + + /// 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 queried. + 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; } - /// Spawns a new process - async_call Spawn { - /// Relative base directory for `path`. - 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; - /// Handle to the spawned process. - out process: Process; - error BadExecutable; - error DiskError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error SystemResources; + /// Returns the base address of the process. + /// + /// This is the address at which the executable image is loaded and relocated to. + syscall get_base_address { + /// The process for which the base address shall be returned. + /// If `null` is passed, the current process will be queried. + in target: ?Process; + + /// The base address of the process. + out base_address: usize; + + /// `target` is not a valid process resource. + error InvalidHandle; } namespace thread { @@ -608,38 +759,157 @@ namespace process { syscall yield { } - /// Terminates the current thread. - syscall exit { - in exit_code: ExitCode; - noreturn; + /// 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; + + error SystemResources; } - /// Waits for the thread to exit and returns its return code. - syscall join { - in _param0: Thread; - out exit_code: ExitCode; + /// 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; + + 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`. - /// The return value is the exit code of the thread. - typedef ThreadFunction = fnptr (?anyptr) u32; + typedef ThreadFunction = fnptr (?anyptr) void; /// Spawns a new thread with `function` passing `arg` to it. - /// If `stack_size` is not 0, will create a stack with the given size. + /// + /// 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 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. + /// NOTE: Cannot happen when `binding` is `unbind`. + 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. 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 normally. + /// - `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; + } + + /// 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; } } @@ -661,6 +931,7 @@ namespace process { /// Stops the process and allows debugging. syscall breakpoint { + //? TODO: Define semantics of a breakpoint } } @@ -700,7 +971,7 @@ namespace process { out memory: [*]u8; /// Is returned when the system is out of memory. - error SystemResource; + error SystemResources; } /// Returns previously allocated memory back to the process heap. @@ -714,42 +985,65 @@ namespace process { } namespace monitor { + /// Queries all process resources. syscall enumerate_processes { - /// NOTE: The process handles will be bound to the calling process with the `at_least_weak` binding + /// NOTE: The process handles will be bound to the calling process with the `at_least_weak` bind operation /// to ensure access. in processes: ?[]Process; + out count: usize; + + error SystemResources; } /// Queries all bound resources by a process. syscall query_bound_resources { in owner: Process; - /// NOTE: The resource handles will be bound to the calling process with the `at_least_weak` binding + + /// NOTE: The return resources will be bound to the calling process with the `at_least_weak` bind operation /// to ensure access. in reslist: ?[]SystemResource; + + out count: usize; + + /// `owner` 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; + + /// `owner` 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; + + /// `owner` 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; + + /// `owner` is not a valid process resource. error InvalidHandle; } } From 4452fdc4332667a0a19dae66f905bd1a4f1dd95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 6 Feb 2026 13:41:32 +0100 Subject: [PATCH 08/42] Clarifies a lot of stuff inside process --- src/abi/src/ashet.abi | 124 ++++++++++++++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 29 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 473720b6..25d90af9 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -585,6 +585,8 @@ namespace process { /// 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; @@ -631,6 +633,11 @@ namespace process { } /// Spawns a new 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. async_call Spawn { /// Relative base directory for `path`. in dir: fs.Directory; @@ -640,11 +647,6 @@ namespace process { /// The arguments passed to the process. /// It is safe to release the resource binding to the current process as soon as this operation returns. - /// - /// NOTE: The kernel will perform a copy of all strings inside `schedule`, so it is safe to - /// reuse the string memory after the schedule operation is performed. - /// - /// This prevents unwanted use-after-free by the kernel. in arguments: []const SpawnArg; /// Handle to the spawned process. @@ -674,7 +676,7 @@ namespace process { /// Completes when the given process terminates. /// - /// NOTE: The call will immediatly complete if `target` is already terminated. + /// NOTE: The call will immediately complete if `target` is already terminated. /// /// NOTE: Awaiting termination of the same process multiple times is idempotent. /// @@ -706,7 +708,7 @@ namespace process { /// 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 a `at_least_weak` bind operation to ensure + /// to the calling process with an `at_least_weak` bind operation to ensure /// resource access. out argv: []const SpawnArg; @@ -764,8 +766,6 @@ namespace process { /// The handle to the current thread. /// NOTE: This handle is ensured to be at least weakly bound to the current process. out handle: Thread; - - error SystemResources; } /// Gets the process for a given thread. @@ -781,6 +781,9 @@ namespace 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; } @@ -839,7 +842,6 @@ namespace process { error InvalidHandle; /// The `target` process is dead, but still has an alive handle. - /// NOTE: Cannot happen when `binding` is `unbind`. error ZombieProcess; error SystemResources; @@ -858,6 +860,8 @@ namespace process { /// /// 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; error InvalidHandle; @@ -897,6 +901,9 @@ namespace process { /// 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. @@ -911,27 +918,61 @@ namespace process { /// 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 { - //? TODO: Define semantics of a breakpoint } } @@ -959,56 +1000,81 @@ 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 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 process resources. + /// 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 bound resources by a process. + /// + /// NOTE: The order of resources is not necessarily stable between calls. syscall query_bound_resources { - in owner: Process; + /// 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; - /// `owner` is not a valid process resource. + /// `proc` is not a valid process resource. error InvalidHandle; error SystemResources; @@ -1021,7 +1087,7 @@ namespace process { /// The total number of bytes the process currently allocates in RAM. out size: usize; - /// `owner` is not a valid process resource. + /// `proc` is not a valid process resource. error InvalidHandle; } @@ -1032,7 +1098,7 @@ namespace process { /// The total number of heap bytes the process currently allocates in RAM. out size: usize; - /// `owner` is not a valid process resource. + /// `proc` is not a valid process resource. error InvalidHandle; } @@ -1043,7 +1109,7 @@ namespace process { /// The total number of heap allocations the process currently has active. out count: usize; - /// `owner` is not a valid process resource. + /// `proc` is not a valid process resource. error InvalidHandle; } } From 2777d7815e232c3698ced1861de80d9c58885740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 6 Feb 2026 14:21:29 +0100 Subject: [PATCH 09/42] Cleans up some wording issues, adds new concept of daemon process and background threads. --- src/abi/src/ashet.abi | 209 ++++++++++++++++++++++++++++++++---------- 1 file changed, 159 insertions(+), 50 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 25d90af9..33205334 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -25,7 +25,7 @@ enum SystemResource : usize /// - 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. +/// - If a tethered resource is destroyed, the associated resource is also destroyed. /// /// NOTE: Every kernel object the userland can interact with is a resource. /// @@ -51,14 +51,14 @@ enum SystemResource : usize /// 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 +/// 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. @@ -153,7 +153,7 @@ namespace resources { /// The resource is strongly bound to the process. /// As long as a single strong binding exists, the resource is - /// valid. + /// valid. item strong = 1; /// The resource is weakly bound to the process. @@ -169,7 +169,7 @@ namespace resources { /// 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`. + /// The kind of binding the resource has on `target`. out binding: Binding; /// The resource or process handle was invalid. @@ -188,7 +188,7 @@ namespace resources { 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`. /// @@ -216,12 +216,12 @@ namespace resources { error SystemResources; } - /// Defines the possible ways of how a resource is tethered to another resource. + /// 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; } @@ -229,7 +229,7 @@ namespace resources { /// 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. + /// will implicitly be destroyed as well. syscall tether { /// The resource which destruction will trigger the tether event. in source: SystemResource; @@ -284,7 +284,7 @@ namespace resources { /// 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.yield`, +/// 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. @@ -311,7 +311,7 @@ namespace resources { /// /// The kernel will modify the `output` and `error` fields of the ARC enclosing structure. /// -/// NOTE: If the owning process is destroyed, all scheduled ARCs of that process are implicitly +/// 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. @@ -329,7 +329,7 @@ namespace overlapped { { /// The type of operation that is performed. /// - /// NOTE: This field is never changed by the kernel. + /// NOTE: This field is never changed by the kernel. field type: Type; /// A user-specified array of pointer-sized fields which may @@ -389,7 +389,7 @@ namespace overlapped { 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 @@ -401,7 +401,7 @@ namespace overlapped { error Completed; /// The kernel does not know the `arc` operation. - error Unscheduled; + error Unscheduled; } enum Thread_Affinity : u8 { @@ -446,7 +446,7 @@ namespace overlapped { /// thread until the request has been completed. This means that other /// threads can continue their work. /// - /// NOTE: If `completed.len` is zero, the operation will never suspend and + /// 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. @@ -456,7 +456,7 @@ namespace overlapped { /// NOTE: Kernel will only touch the first `completed_count` elements and /// keeps the rest unchanged. in completed: []*ARC; - + /// Defines how the operation will suspend. in block_mode: BlockMode; @@ -503,7 +503,7 @@ namespace overlapped { /// The number of ARCs completed inside `events`. out completed_count: usize; - + /// Another `await_any_of` already awaits an event from `events`. error InvalidOperation; @@ -542,9 +542,17 @@ namespace process { /// /// NOTE: Destroying an active process resource will terminate the process and immediately reap the /// zombie. - /// - /// NOTE: A process may have zero associated threads, but still be active. This means it exists - /// and still may keep resources alive, but it cannot act anymore. + /// + /// 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)` @@ -611,11 +619,11 @@ namespace process { /// 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. @@ -632,16 +640,19 @@ namespace process { } } - /// 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 + /// 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: fs.Directory; - + /// File name of the executable relative to `dir`. in path: str; @@ -651,7 +662,7 @@ namespace process { /// Handle to the spawned process. out process: Process; - + error BadExecutable; error DiskError; error FileNotFound; @@ -688,8 +699,11 @@ namespace process { /// The reason why the process was terminated. out reason: TerminationReason; - /// Contains the value passed to `terminate.success` or `false` if - /// not terminated by `terminate`. + /// 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. @@ -699,9 +713,9 @@ namespace process { /// 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 queried. + /// 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 @@ -711,24 +725,23 @@ namespace process { /// 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 queried. + /// 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 + /// 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. /// @@ -745,7 +758,7 @@ namespace process { /// This is the address at which the executable image is loaded and relocated to. syscall get_base_address { /// The process for which the base address shall be returned. - /// If `null` is passed, the current process will be queried. + /// If `null` is passed, the current process will be used. in target: ?Process; /// The base address of the process. @@ -755,6 +768,48 @@ namespace process { error InvalidHandle; } + /// 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. @@ -802,6 +857,21 @@ namespace process { /// 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 @@ -814,13 +884,13 @@ namespace process { /// 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 + /// 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; @@ -832,6 +902,9 @@ namespace process { /// bytes. in stack_size: usize; + /// 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. @@ -879,7 +952,7 @@ namespace process { in target: Thread; /// Informs how the thread exited: - /// - `true`: The thread exited by its thread function returning normally. + /// - `true`: The thread exited by its thread function returning. /// - `false`: The thread exited by invoking `exit` or `kill`. out regular_exit: bool; @@ -911,7 +984,7 @@ namespace process { /// 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. + /// NOTE: Resuming an already active thread is idempotent and does nothing. syscall resume { /// The thread that should be resumed. in target: Thread; @@ -922,6 +995,42 @@ namespace process { /// `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 { @@ -929,19 +1038,19 @@ namespace process { /// 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; } @@ -1005,7 +1114,7 @@ namespace process { /// usable in the returned `memory`. in size: usize; - /// The alignment of the pointer encoded as the number of + /// 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 @@ -1068,7 +1177,7 @@ namespace process { /// 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. @@ -1268,7 +1377,7 @@ namespace datetime { } /// Completes when `datetime.now() >= when`. - /// + /// /// NOTE: A call to `set` may trigger all active alarm calls that /// are now satisfied. async_call Alarm { @@ -1341,7 +1450,7 @@ namespace datetime { { /// The point in time to query the zone offset for. in dt: DateTime; - + /// The local time offset in minutes. out minutes: i16; @@ -1469,7 +1578,7 @@ namespace datetime { /// NOTE: This may happen due to daylight saving time or similar rules. in occurrence: DuplicateTimeOccurrence; - /// The date/time value representing the given date. + /// The date/time value representing the given date. out dt: DateTime; /// The gregorian date contains an invalidly specified date. @@ -1484,7 +1593,7 @@ namespace datetime { error AmbiguousLocalTime; } - /// Enumeration of the variants how missing wall clock times will be resolved. + /// 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; @@ -4606,7 +4715,7 @@ namespace io { //? //? Global Types -//? +//? struct Point { From 035295cd774656f65834ec14ac821fd23935b49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 6 Feb 2026 14:43:20 +0100 Subject: [PATCH 10/42] Introduces create_empty_process to enable custom application loaders or daemon creation. --- src/abi/src/ashet.abi | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 33205334..4eb24b0f 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -671,6 +671,43 @@ namespace process { error SystemResources; } + /// 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; + + /// 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; + + /// Handle to the created process. + out process: Process; + + /// A handle in `arguments` is not a valid resource handle. + error InvalidHandle; + + error SystemResources; + } + enum TerminationReason : u8 { /// The process terminated properly through a call to `terminate`. item regular_exit = 0; @@ -756,6 +793,8 @@ namespace process { /// 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. @@ -766,6 +805,9 @@ namespace process { /// `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. From 267fb8c7255eb76192804098fa57b4fda86b97e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 6 Feb 2026 22:13:20 +0100 Subject: [PATCH 11/42] Overhauls random and shm namespace docs. --- src/abi/src/ashet.abi | 113 ++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 37 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 4eb24b0f..87622e18 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1671,6 +1671,7 @@ namespace datetime { } } +//? TODO: Review this namespace. namespace video { resource VideoOutput { } @@ -1744,24 +1745,38 @@ namespace video { } } +/// This namespace contains items related to entropy management. namespace random { - /// Fills the provided pointer with random bytes from the entropy pool. + + /// 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). /// - /// If the entropy pool runs out of entropy bits, this call does not block - /// and draw bytes from the pool. + /// 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; } - /// Fills the provided buffer with given length amount of random bytes. + /// Fills `data` with random bytes, but only after the kernel DRBG is seeded. /// - /// This call blocks until the entropy pool has enough entropy to fill - /// an entire hash to draw from. + /// 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; } + + //? TODO: add "add entropy" syscall } +//? TODO: Rework this namespace into using InputGroups, DeviceIds, ... +//? TODO: Review this namespace. namespace input { union InputEvent { field event_type: Type; @@ -2644,6 +2659,7 @@ namespace input { } } +//? TODO: Review this namespace. namespace network { enum IP_Type : u8 { item ipv4; @@ -2862,6 +2878,7 @@ namespace network { } +//? TODO: Review this namespace. /// 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. @@ -3226,32 +3243,49 @@ namespace fs { } +/// 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 can be written by all processes without any memory protection. + /// 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 number of bytes inside the given shared memory object. - syscall get_length { + /// 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; - out length: usize; - error InvalidHandle; - } - /// Returns a pointer to the shared memory. - syscall get_pointer { - in handle: SharedMemory; - out pointer: [*]align(16) u8; + /// The memory region of the shared memory object. + out memory: []align(16) u8; + + /// The `handle` is not a valid shared memory object. error InvalidHandle; } } +//? TODO: Review this namespace. namespace pipe { resource Pipe { } @@ -3322,6 +3356,7 @@ namespace pipe { } +//? TODO: Review this namespace. namespace sync { resource SyncEvent { } @@ -3378,6 +3413,7 @@ namespace sync { } } +//? TODO: Review this namespace. namespace draw { resource Font { } @@ -3526,6 +3562,7 @@ namespace draw { } +//? TODO: Review this namespace. namespace gui { resource Window { } @@ -4248,6 +4285,7 @@ namespace gui { } } +//? TODO: Review this namespace. namespace service { resource Service { } @@ -4293,6 +4331,7 @@ namespace service { } } +//? TODO: Review this namespace. /// /// The I/O namespace contains APIs to interface with external hardware like serial ports, I²C busses and so on. /// @@ -4306,35 +4345,35 @@ namespace io { ... } - //? syscall enumerate { - //? in list: []SerialPortID; + syscall enumerate { + in list: []SerialPortID; - //? out count: usize; - //? } + 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. @@ -4362,7 +4401,7 @@ namespace io { //? TODO: /// NOTE: This is usually the same as `sw_control_flow_rx`. //? TODO: 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; From 777673836fbceca67857f46fa4b6b89c207c0e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 6 Feb 2026 23:25:57 +0100 Subject: [PATCH 12/42] Reworks pipe namespace --- src/abi/src/ashet.abi | 162 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 29 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 87622e18..f0dddc66 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -3285,75 +3285,179 @@ namespace shm { } } -//? TODO: Review this namespace. +/// 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 { } - enum PipeMode : u8 { - /// Completes immediately 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; - } - - /// Spawns a new pipe with `fifo_length` elements of `object_size` bytes. + /// 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 + /// if a `Read` call is active. Otherwise, up to `fifo_length` elements can be /// stored in a FIFO. syscall create { - in object_size: usize; + /// 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; - error SystemResources; + + /// 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 objects stored in the pipe. - syscall get_object_size { + /// 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; + 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; + /// + /// 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; - /// Numbert of elements written into the pipe. - out count: usize; + 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 pipes element size + + /// Distance between each element in `buffer`. Can be different from the pipe's element size /// to allow sparse data to be transferred. - /// If `0`, it will use the `object_size` property of the pipe. + /// + /// 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 read. + + /// 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; + } } //? TODO: Review this namespace. From 93a0fbd94bc5013d55533cf0287a805109c95efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sat, 7 Feb 2026 00:09:03 +0100 Subject: [PATCH 13/42] Cleans up the sync namespace. --- src/abi/src/ashet.abi | 110 +++++++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index f0dddc66..0644f317 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -3460,59 +3460,111 @@ namespace pipe { } } -//? TODO: Review this namespace. +/// This namespace contains items related to synchronization between multiple threads. namespace sync { - resource SyncEvent { } - + /// 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; + } + + /// 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 one `WaitForEvent` IOP waiting for the given event. + /// 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` IOP waiting for the given event. + /// 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; - error InvalidHandle; - } - - /// Creates a new mutual exclusion. - syscall create_mutex { - error SystemResources; - out mutex: Mutex; - } - - /// Tries to lock a mutex and returns if it was successful. - syscall try_lock { - in mutex: Mutex; - out is_locked: bool; - error InvalidHandle; - } - /// Unlocks a mutual exclusion. Completes a single `Lock` IOP if it exists. - syscall unlock { - in mutex: Mutex; - error InvalidHandle; - } - - /// Locks a mutex. Will complete once the mutex is locked. - async_call Lock { - in mutex: Mutex; + /// `event` is not a valid sync event. error InvalidHandle; } } From c943e505a2353d21c8fe466a882c2529c0901840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sat, 7 Feb 2026 00:27:12 +0100 Subject: [PATCH 14/42] Adds a working plan for the video output namespace. --- src/abi/src/ashet.abi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 0644f317..c64bc3ac 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1673,6 +1673,11 @@ namespace datetime { //? TODO: Review this namespace. namespace video { + //? TODO: I guess i could make the VideoOutput resource not have a video memory exposed, but only provide + //? an async call "WritePixels" which can download a buffer into a video card/framebuffer/whatever. + //? I can also offer a MemoryMapping over a VideoOutput, which exposes a back or frontbuffer, a flush + //? method and a way of getting the pixel data. The creation of this may fail. + resource VideoOutput { } /// Index of the systems video outputs. From de442600fb36dff6e4738b159183b9289555334d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sat, 7 Feb 2026 00:41:53 +0100 Subject: [PATCH 15/42] Cleans some basic issues in io namespace. --- src/abi/src/ashet.abi | 57 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index c64bc3ac..60492717 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1752,7 +1752,6 @@ namespace video { /// 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. @@ -4507,8 +4506,7 @@ namespace io { } syscall enumerate { - in list: []SerialPortID; - + in list: ?[]SerialPortID; out count: usize; } @@ -4544,23 +4542,23 @@ 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; @@ -4594,13 +4592,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; @@ -4642,7 +4640,7 @@ namespace io { } /// Writes data to the serial port. - async_call write { + async_call Write { in port: SerialPort; in data: bytestr; @@ -4656,7 +4654,7 @@ namespace io { } /// Reads data from a serial port. - async_call read { + async_call Read { in port: SerialPort; in data: bytebuf; @@ -4682,7 +4680,7 @@ 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: clock.Duration; @@ -4698,6 +4696,7 @@ namespace io { item none = 0; item break_detected = 1; item parity_error = 2; + item framing_error = 3; } enum StopBits : u8 { @@ -4710,10 +4709,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`. @@ -4807,7 +4806,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; @@ -4861,7 +4860,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`. From 57a7e697b71286aace6c3b882e3da132b4f12fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sat, 7 Feb 2026 09:07:29 +0100 Subject: [PATCH 16/42] Starts working on the video namespace. --- src/abi/src/ashet.abi | 155 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 29 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 60492717..34cadc49 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1687,25 +1687,6 @@ namespace video { ... } - 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; - - /// Length of a scanline. - field stride: usize; - - /// Number of valid elements in a scanline - field width: u16; - - /// Number of valid scanlines. - field height: u16; - } - /// Returns a list of all video outputs. /// /// If `ids` is `null`, the total number of available outputs is returned; @@ -1732,22 +1713,139 @@ namespace video { error InvalidHandle; } - /// 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. /// /// This allows frame-synchronized presentation of video data. + /// + /// NOTE: All scheduled `WaitForVBlank` operations complete at the start of the next vblank period. async_call WaitForVBlank { in output: VideoOutput; error InvalidHandle; } + + /// Specifies how `WritePixels` will upload the pixels. + enum PresentMode : u8 { + /// The pixel data is written immediately. + /// + /// NOTE: This mode will immediatly upload the pixel data and + /// will not await a vblank period. This means the upload + /// is likely to create visual glitches or tearing. + item immediate = 0; + + /// The kernel implicitly waits for the vblank period of the + /// scanout before writing the pixel data. + /// + /// 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. + /// + /// LORE: Originally, we had the abiltiy 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 failible without removing the ability for a generic + /// upload procedure. + async_call WritePixels { + /// The output which should receive the pixel data. + in output: VideoOutput; + + /// The portion of the video buffer that should be updated. + in destination: Rectangle; + + /// The pixel data that should be uploaded. + /// Each scanline starts at `y * stride` elements apart and the buffer must contain + /// at least `destination.height` scanlines. + in pixels: []const Color; + + /// The length of a scanline in `pixels`. + in stride: usize; + + /// Determines when to perform the pixel data write. + in mode: PresentMode; + + /// `output` is not a valid video output resource. + error InvalidHandle; + + /// `pixels.len` is less than `stride * destination.height`. + error BufferSize; + + /// `stride` is less than `destination.width`. + error InvalidStride; + + /// `destination` is outside the actual video buffer resolution. + error InvalidRegion; + } + + resource Buffer { } + + enum BufferKind : u8 { + /// `Present` is a no-op. + item front_buffer = 0; + + /// Requires `Present` to make the changes visible. + item back_buffer = 0; + } + + syscall create_buffer { + in output: VideoOutput; + in requested_kind: BufferKind; + + out buffer: Buffer; + + error InvalidHandle; + error Unsupported; + error SystemResources; + } + + /// Applies the changes inside `buffer` and guarantees they + /// are visible afterwards. + async_call Present + { + in buffer: Buffer; + + /// Determines when to perform the pixel data update. + in mode: PresentMode; + + error InvalidHandle; + } + + 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; + + /// Length of a scanline. + field stride: usize; + + /// Number of valid elements in a scanline + field width: u16; + + /// Number of valid scanlines. + field height: u16; + } + + /// 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: Buffer; + out memory: VideoMemory; + error InvalidHandle; + } } /// This namespace contains items related to entropy management. @@ -4691,7 +4789,6 @@ namespace io { error Unsupported; } - enum SerialPortError : u8 { item none = 0; item break_detected = 1; From c65cd5ad8aee7a1eaa5ee99eda42c1235429b213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sun, 8 Feb 2026 10:25:51 +0100 Subject: [PATCH 17/42] Overhauls the video namespace a lot --- src/abi/src/ashet.abi | 182 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 147 insertions(+), 35 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 34cadc49..d18a4782 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -298,6 +298,8 @@ namespace resources { /// 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*. @@ -317,6 +319,9 @@ namespace resources { /// 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. /// @@ -1671,15 +1676,8 @@ namespace datetime { } } -//? TODO: Review this namespace. +/// This namespace contains items related to presenting video data. namespace video { - //? TODO: I guess i could make the VideoOutput resource not have a video memory exposed, but only provide - //? an async call "WritePixels" which can download a buffer into a video card/framebuffer/whatever. - //? I can also offer a MemoryMapping over a VideoOutput, which exposes a back or frontbuffer, a flush - //? method and a way of getting the pixel data. The creation of this may fail. - - resource VideoOutput { } - /// Index of the systems video outputs. enum VideoOutputID : u8 { /// The primary video output @@ -1697,19 +1695,36 @@ namespace video { out count: usize; } + /// 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 { } + /// Acquire exclusive access to a video output. syscall acquire { in output_id: VideoOutputID; + + /// The resource created from `output_id`. out output: VideoOutput; - error NotAvailable; - error NotFound; + + /// Exclusive access is already held for the video output identified by `output_id`. + error AlreadyExists; + + /// `output_id` is not a valid video output id. + error InvalidId; + error SystemResources; } - /// Returns the current resolution + /// Returns the resolution of `output` in pixels. syscall get_resolution { in output: VideoOutput; + out resolution: Size; + + /// `output` is not a valid video output resource. error InvalidHandle; } @@ -1717,9 +1732,14 @@ namespace video { /// /// This allows frame-synchronized presentation of video data. /// - /// NOTE: All scheduled `WaitForVBlank` operations complete at the start of the next vblank period. + /// 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; + + /// `output` is not a valid video output resource. error InvalidHandle; } @@ -1727,13 +1747,15 @@ namespace video { enum PresentMode : u8 { /// The pixel data is written immediately. /// - /// NOTE: This mode will immediatly upload the pixel data and - /// will not await a vblank period. This means the upload - /// is likely to create visual glitches or tearing. + /// 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; - /// The kernel implicitly waits for the vblank period of the - /// scanout before writing the pixel data. + /// 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. @@ -1742,7 +1764,10 @@ namespace video { /// Uploads pixels to a video output. /// - /// LORE: Originally, we had the abiltiy to directly get a pointer + /// 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 @@ -1753,7 +1778,7 @@ namespace video { /// 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 failible without removing the ability for a generic + /// 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. @@ -1762,12 +1787,17 @@ namespace video { /// The portion of the video buffer that should be updated. in destination: Rectangle; - /// The pixel data that should be uploaded. - /// Each scanline starts at `y * stride` elements apart and the buffer must contain - /// at least `destination.height` scanlines. + /// 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; - /// The length of a scanline in `pixels`. + /// The length of a scanline in `pixels` in elements. in stride: usize; /// Determines when to perform the pixel data write. @@ -1776,7 +1806,11 @@ namespace video { /// `output` is not a valid video output resource. error InvalidHandle; - /// `pixels.len` is less than `stride * destination.height`. + /// 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; /// `stride` is less than `destination.width`. @@ -1786,39 +1820,108 @@ namespace video { error InvalidRegion; } - resource Buffer { } + /// 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 { } enum BufferKind : u8 { - /// `Present` is a no-op. + /// 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; - /// Requires `Present` to make the changes visible. - item back_buffer = 0; + /// 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; } - syscall create_buffer { + /// 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; + + /// Which buffer should be mapped. in requested_kind: BufferKind; - out buffer: Buffer; + out buffer: BufferMapping; + /// `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; } /// 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 { - in buffer: Buffer; + /// 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; } + /// 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. /// @@ -1828,7 +1931,7 @@ namespace video { /// There are `height` total scanlines available. field base: [*]align(4) Color; - /// Length of a scanline. + /// Length of a scanline in elements. field stride: usize; /// Number of valid elements in a scanline @@ -1839,11 +1942,20 @@ namespace video { } /// 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. + /// + /// 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 output: Buffer; + in buffer: BufferMapping; + + /// The descriptor of the memory mapped video buffer. out memory: VideoMemory; + + /// `buffer` is not a valid buffer mapping resource. error InvalidHandle; } } From b331ced7b606ef5260338c556296d0a28685e029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sun, 8 Feb 2026 13:29:27 +0100 Subject: [PATCH 18/42] Adds three TODOs related to audio, sync and clock namespace. --- src/abi/src/ashet.abi | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index d18a4782..282649f3 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1276,6 +1276,11 @@ namespace process { /// 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 { item system_start = 0; @@ -1960,6 +1965,18 @@ namespace video { } } +//? 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. +} + /// This namespace contains items related to entropy management. namespace random { /// Fills `data` with random bytes. @@ -3718,6 +3735,9 @@ namespace sync { /// `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. From b11fc045c19d6f4b79b1b0bf1386fa6413d8cc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sun, 8 Feb 2026 15:01:32 +0100 Subject: [PATCH 19/42] Heavily reworks how the input subsystem of the OS should work --- src/abi/src/ashet.abi | 698 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 617 insertions(+), 81 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 282649f3..a1f0de08 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -2006,13 +2006,484 @@ namespace random { //? TODO: add "add entropy" syscall } -//? TODO: Rework this namespace into using InputGroups, DeviceIds, ... -//? TODO: Review this namespace. +/// +/// 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 { - union InputEvent { - field event_type: Type; - field mouse: MouseEvent; - field keyboard: KeyboardEvent; + /// + /// 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 only 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. + /// + /// 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; + + ... + } + + /// 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; + } + + + /// 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; + ... + } + + /// 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; + ... + } + + /// 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; + } + + /// 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 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; + } + + /// 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; + } + + + /// Waits for the next event emitted by a specific device. + /// + /// 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: 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`. + 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; + + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + + /// `device` is `DeviceId.synthetic`. + error BadDevice; + } + + /// 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; + } + + /// 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, the group will never buffer events. + /// `GetEvent` will still work but will only ever complete on newly arriving events. + in queue_size: usize; + + out group: InputGroup; + + error SystemResources; + } + + /// 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; + + /// `group` is not a valid input group resource. + error InvalidHandle; + + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + } + + /// 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; + + /// `group` is not a valid input group resource. + error InvalidHandle; + + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + } + + + /// 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; + } + + /// 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 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; + + /// `device` is not valid anymore (e.g. device removed) or was never valid. + error InvalidDevice; + + error SystemResources; + } + + + + /// Waits for the next queued event from an input group. + /// + /// 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; + + out event: InputEvent; + + /// Number of events dropped since the last successful dequeue from this group. + out dropped_since_last: u32; + + /// `group` is not a valid input group handle. + error InvalidHandle; + + /// A `GetEvent` operation for `group` is already scheduled. + error NonExclusiveAccess; + } + + /// Pushes a synthetic event into a group. + /// + /// 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()` + /// + /// 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; + + /// Returned when `force == false` and enqueueing would drop an existing queued event. + error Overflow; + } + + /// 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; + + /// `group` is not a valid input group resource. + error InvalidHandle; + + /// `queries[i].what` contains an unknown value. + error InvalidValue; + } + + /// A single state query item. + /// + /// The kernel reads `what` and `which`, and writes `value`. + /// + /// 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. + /// + struct StateQuery { + /// Defines what kind of input should be queried. + field what: Item; + + /// Defines which instance of `what` should be queried. + field which: u16; + + /// Output value filled by the kernel. + field value: i16; + + /// Selects which state component is queried. + enum Item : u16 { + /// `which` is a `KeyUsageCode`. + item keyboard_key = 0; + + /// `which` is a `MouseButton` value. + item mouse_button = 1; + + /// `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; + } + } + + /// Flags attached to an input event. + bitstruct EventFlags : u16 { + /// Set for events that did not originate from a device driver. + field synthetic: bool; + + reserve u15 = 0; + } + + /// An input event as delivered to userland. + struct InputEvent { + /// The type of event that was emitted. + field type: Type; + + /// 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; + + /// The originating device id or `DeviceId.synthetic` if none. + field device: DeviceId; + + /// Event flags. + field flags: EventFlags; + + /// The event payload. + field payload: Payload; enum Type : u16 { item key_press = 0; @@ -2022,9 +2493,149 @@ namespace input { item mouse_abs_motion = 3; item mouse_button_press = 4; item mouse_button_release = 5; + item mouse_wheel = 6; + + item digital_button_press = 7; + item digital_button_release = 8; + + item rel_axis_motion = 9; + item abs_axis_motion = 10; + + ... + } + + union Payload { + field keyboard: Keyboard; + + field mouse_rel_motion: MouseRelMotion; + field mouse_abs_motion: MouseAbsMotion; + field mouse_button: MouseButton; + field mouse_wheel: MouseWheel; + + field digital_button: Button; + + field rel_axis: RelAxis; + field abs_axis: AbsAxis; + } + + /// 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; + } + + /// 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; } + + struct MouseButton { + /// Which mouse button was pressed/released. + field button: input.MouseButton; + } + + /// 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; + } + + 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; + + /// 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. + /// + /// 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 expansible. + /// + field text: ?str; + + /// The key in this event was pressed or released + field pressed: bool; + + /// The modifier keys currently active + field modifiers: KeyboardModifiers; + } + + /// 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; + } + + /// 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; + } + + /// 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; } + /// Keyboard modifier state accompanying key events. bitstruct KeyboardModifiers : u16 { field shift: bool; field alt: bool; @@ -2040,30 +2651,6 @@ namespace input { reserve u5 = 0; } - - //? TODO: Implement the idea of "input devices" which can be queried - //? and waited for in batches (wait for one even from several - //? devices in an input group) - - /// Waits for an input event and completes when any input was done. - async_call GetEvent { - out event: InputEvent; - error InProgress; - error NonExclusiveAccess; - } - - - 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; - } - /// /// This is an enumeration of all well-known HID Keyboard/Keypad Page (0x07) usage codes for /// keys. @@ -5304,54 +5891,3 @@ bitstruct Color : u8 { struct UUID { field bytes: [16]u8; } - -/// Event structures shared between different event groups -union SharedEventType { - field input: input.InputEvent.Type; - field widget: gui.WidgetEvent.Type; - field window: gui.WindowEvent.Type; -} - -struct MouseEvent { - field event_type: SharedEventType; //? MUST BE FIRST! - - field x: i16; - field y: i16; - field dx: i16; - field dy: i16; - field button: input.MouseButton; -} - -struct KeyboardEvent { - field event_type: SharedEventType; //? MUST BE FIRST! - - /// The raw usage code for the key. Meaning depends on the layout; - /// kinda represents the physical position on the keyboard. - field usage: input.KeyUsageCode; - - /// 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 key in this event was pressed or released - field pressed: bool; - - /// The modifier keys currently active - field modifiers: input.KeyboardModifiers; -} From 0780b71b099f48c2763f36d61090f3304db5fc9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sun, 8 Feb 2026 21:05:05 +0100 Subject: [PATCH 20/42] Cleans the input namespace. --- src/abi/src/ashet.abi | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index a1f0de08..26ae7873 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -2033,6 +2033,8 @@ namespace random { /// //? 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. + /// /// Opaque identifier for an input device. /// @@ -2043,7 +2045,7 @@ namespace input { /// 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 only guaranteed to be valid only + /// 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. @@ -2133,7 +2135,7 @@ namespace input { /// NOTE: The value for these axes will be normalized by the kernel. /// /// NOTE: This includes axes like joysticks which have a zero position, - /// only only change their reported value in a reproducible manner. + /// only change their reported value in a reproducible manner. /// Same position = Same value. field abs_axes_cnt: u16; @@ -2189,6 +2191,9 @@ namespace input { /// 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; @@ -2258,12 +2263,14 @@ namespace input { syscall create_group { /// Maximum number of events buffered by this group. /// - /// NOTE: If `queue_size` is zero, the group will never buffer events. - /// `GetEvent` will still work but will only ever complete on newly arriving events. + /// 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; } @@ -2318,7 +2325,7 @@ namespace input { /// /// NOTE: This returns only groups that are visible to the calling process. /// - /// NOTE: The group resources returned in `groups` will bound to the calling process with + /// 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; @@ -2331,8 +2338,6 @@ namespace input { error SystemResources; } - - /// Waits for the next queued event from an input group. /// /// The operation completes when: @@ -2568,7 +2573,7 @@ namespace input { /// 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 + /// 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 @@ -2584,13 +2589,10 @@ namespace input { /// 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. + /// This method is flexible enough to be future-proof and extensible. /// field text: ?str; - /// The key in this event was pressed or released - field pressed: bool; - /// The modifier keys currently active field modifiers: KeyboardModifiers; } @@ -2783,7 +2785,7 @@ namespace input { /// NOTE: Typically remapped for other languages in the host system. item @"5" = 0x22; - /// Keyboard `6` and `∧` + /// Keyboard `6` and `^` /// NOTE: Typically remapped for other languages in the host system. item @"6" = 0x23; @@ -2860,11 +2862,11 @@ namespace input { /// NOTE: Typically remapped for other languages in the host system. item semicolon = 0x33; - /// Keyboard `'` and `“` + /// Keyboard `'` and `"` /// NOTE: Typically remapped for other languages in the host system. item apostrophe = 0x34; - /// Keyboard Grave Accent (`^`) and Tilde (`~`) + /// Keyboard Grave Accent (`\``) and Tilde (`~`) /// NOTE: Typically remapped for other languages in the host system. item grave_accent = 0x35; @@ -3401,7 +3403,7 @@ namespace input { item kp_at = 0xCE; /// Keypad `!` - item kp_exlamation = 0xCF; + item kp_exclamation = 0xCF; /// Keypad Memory Store item kp_memory_store = 0xD0; From 9355d04a5973e2a53f9e2b4634a79a55d8c1abcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sun, 8 Feb 2026 23:04:56 +0100 Subject: [PATCH 21/42] Updates the comments in the draw namespace and refines some semantics. --- assets/fonts/planned.txt | 2 + src/abi/src/ashet.abi | 130 ++++++++++++++++++++++++++++++++++----- 2 files changed, 118 insertions(+), 14 deletions(-) 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 26ae7873..665da94a 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -2052,6 +2052,10 @@ namespace input { /// /// 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 @@ -2506,6 +2510,8 @@ namespace input { item rel_axis_motion = 9; item abs_axis_motion = 10; + //? TODO: touch_down, touch_up, touch_move + ... } @@ -4392,16 +4398,12 @@ namespace sync { } } -//? TODO: Review this namespace. +/// 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 { } - enum FontType : u32 { - item bitmap = 0; - item vector = 1; - ... - } - /// A framebuffer is something that can be drawn on. resource Framebuffer { } @@ -4422,26 +4424,58 @@ namespace draw { item widget = 3; } - /// Returns the font data for the given font name, if any. + /// 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; } @@ -4454,61 +4488,107 @@ namespace draw { 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 offscreen painting. + /// 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. - /// Other framebuffer types are not allowed to be passed. + /// 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; } @@ -4516,29 +4596,51 @@ namespace draw { /// 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 function will run asynchronously and will return as soon as the rendering is done. + /// The operation will complete when rendering is done. /// - /// NOTE: On machines without hardware acceleration, this syscall might be completed synchronously. + /// 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. From 73e96fde971f2ab4fa24008bf79ec1a49991a575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sun, 8 Feb 2026 23:44:13 +0100 Subject: [PATCH 22/42] Starts reworking 'fs' namespace. --- src/abi/src/ashet.abi | 112 ++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 665da94a..69f43dd1 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -669,7 +669,7 @@ namespace process { out process: Process; error BadExecutable; - error DiskError; + error IoError; error FileNotFound; error InvalidHandle; error InvalidPath; @@ -3707,7 +3707,7 @@ namespace network { //? TODO: Review this namespace. /// 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. +/// that are actually typeable on the operating system though. /// /// There are some special file names: /// - `.` is the "current directory" selector and does not add to the path. @@ -3753,10 +3753,11 @@ namespace fs { const max_fs_type_len = 32; /// 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; @@ -3831,12 +3832,31 @@ namespace fs { field modified_date: datetime.DateTime; } + /// A directory is a group of files and other directories in a file system. + resource Directory { } + /// A file is a handle to a binary data storage that is stored + /// in a file system. + /// + /// Files are byte-addressed. resource File { } - resource Directory { } + /// A file system location is a fusion of a directory handle with + /// an associated relative path. + /// + /// 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 { } - //? TODO: Implement the idea of a "file system location", a type that binds a directory + relative path together + //? TODO: Add operations for locations /// Finds a file system by name syscall find_filesystem { @@ -3846,7 +3866,7 @@ namespace fs { /// Flushes all open files to disk. async_call Sync { - error DiskError; + error IoError; } /// Gets information about a file system. @@ -3856,7 +3876,7 @@ namespace fs { in fs_id: FileSystemId; out info: FileSystemInfo; out next: FileSystemId; - error DiskError; + error IoError; error InvalidFileSystem; } @@ -3865,12 +3885,11 @@ namespace fs { in fs_id: FileSystemId; in path: str; out dir: Directory; - error DiskError; + error IoError; error FileNotFound; error InvalidFileSystem; error InvalidPath; error NotADir; - error SystemFdQuotaExceeded; error SystemResources; } @@ -3879,12 +3898,11 @@ namespace fs { in start_dir: Directory; in path: str; out dir: Directory; - error DiskError; + error IoError; error FileNotFound; error InvalidHandle; error InvalidPath; error NotADir; - error SystemFdQuotaExceeded; error SystemResources; } @@ -3894,21 +3912,44 @@ namespace fs { error InvalidHandle; } - /// resets the directory iterator to the starting point - async_call ResetDirEnumeration { - in dir: Directory; - error DiskError; + /// A directory enumerator allows enumerating elements inside + /// of a directory. + /// + /// NOTE: A directory enumerator is not unwindable/resettable + /// as it's typically simpler to destroy the enumerator + /// and create a new one than maintaining stateful directory + /// content tracking. + resource DirEnumerator { } + + syscall create_enumerator { + in dir: Dir; + + out enumerator: DirEnumerator; + + /// `dir` is not a valid directory resource. error InvalidHandle; + error SystemResources; } - /// 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; + /// This operation returns the FileInfo for the next file or directory inside + /// the `enumerator`. + async_call GetNextDirItem { + in enumerator: DirEnumerator; + out info: FileInfo; + + /// Returned when the enumerator reached the end of the directory. + /// + /// NOTE: The `enumerator` resource can be destroyed after this error is + /// returned as there is no way to unwind an enumerator resource. + error EndOfDirectory; + error InvalidHandle; + + /// The enumerator failed to advance to the next item as the underlying + /// filesystem failed. + error IoError; + error SystemResources; } @@ -3917,7 +3958,7 @@ namespace fs { in dir: Directory; in path: str; in recurse: bool; - error DiskError; + error IoError; error FileNotFound; error InvalidHandle; error InvalidPath; @@ -3930,7 +3971,7 @@ namespace fs { in path: str; in mkopen: bool; out new_dir: Directory; - error DiskError; + error IoError; error Exists; error InvalidHandle; error InvalidPath; @@ -3941,7 +3982,7 @@ namespace fs { in dir: Directory; in path: str; out info: FileInfo; - error DiskError; + error IoError; error FileNotFound; error InvalidHandle; error InvalidPath; @@ -3953,7 +3994,7 @@ namespace fs { in src_dir: Directory; in src_path: str; in dst_path: str; - error DiskError; + error IoError; error Exists; error FileNotFound; error InvalidHandle; @@ -3967,7 +4008,7 @@ namespace fs { in src_path: str; in dst_dir: Directory; in dst_path: str; - error DiskError; + error IoError; error Exists; error FileNotFound; error InvalidHandle; @@ -3981,7 +4022,7 @@ namespace fs { in src_path: str; in dst_dir: Directory; in dst_path: str; - error DiskError; + error IoError; error Exists; error FileNotFound; error InvalidHandle; @@ -3996,14 +4037,12 @@ namespace fs { in access: FileAccess; in mode: FileMode; out handle: File; - error DiskError; - error Exists; + error IoError; error FileAlreadyExists; error FileNotFound; error InvalidHandle; error InvalidPath; error NoSpaceLeft; - error SystemFdQuotaExceeded; error SystemResources; error WriteProtected; } @@ -4011,7 +4050,7 @@ namespace fs { /// closes the handle and flushes the file. async_call CloseFile { in file: File; - error DiskError; + error IoError; error InvalidHandle; error SystemResources; } @@ -4019,7 +4058,7 @@ namespace fs { /// makes sure this file is safely stored to mass storage device async_call FlushFile { in file: File; - error DiskError; + error IoError; error InvalidHandle; error SystemResources; } @@ -4030,7 +4069,7 @@ namespace fs { in offset: u64; in buffer: bytebuf; out count: usize; - error DiskError; + error IoError; error InvalidHandle; error SystemResources; } @@ -4041,7 +4080,7 @@ namespace fs { in offset: u64; in buffer: bytestr; out count: usize; - error DiskError; + error IoError; error InvalidHandle; error NoSpaceLeft; error SystemResources; @@ -4052,7 +4091,7 @@ namespace fs { async_call StatFile { in file: File; out info: FileInfo; - error DiskError; + error IoError; error InvalidHandle; error SystemResources; } @@ -4061,12 +4100,11 @@ namespace fs { async_call Resize { in file: File; in length: u64; - error DiskError; + error IoError; error InvalidHandle; error NoSpaceLeft; error SystemResources; } - } /// This namespace contains items related to shared memory objects. From d0c5591b16bb7faca74c5f87f92b2ce14ab226a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 9 Feb 2026 23:38:53 +0100 Subject: [PATCH 23/42] Streamlines fs namespace a lot --- src/abi/src/ashet.abi | 1093 +++++++++++++++++++++++++++++++++-------- 1 file changed, 889 insertions(+), 204 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 69f43dd1..5d8a1d03 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -3704,43 +3704,89 @@ namespace network { } -//? TODO: Review this namespace. /// 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. /// -/// There are some special file names: +/// File names are measured in bytes (not Unicode codepoints). A single path segment +/// must not exceed `fs.max_file_name_len` bytes. +/// +/// There are some reserved 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. +/// Paths: +/// +/// The filesystem kernel API only accepts *relative* paths. /// -/// 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. +/// A kernel path is a UTF-8 string composed of a sequence of names separated by one or more `/`. +/// +/// 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` -/// - `SYS:/apps/editor/code` -/// - `USB0:/foo/../bar` (which is equivalent to `USB0:/bar`) +/// - `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`) /// -/// 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: 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` @@ -3754,356 +3800,995 @@ namespace fs { /// The maximum number of bytes in a file name. /// - /// As some programs use sha256 checksums and 64 bytes are enough to store - /// a hex-encoded 256 bit sequence: - /// - `114ac2caf8fefad1116dbfb1bd68429f68e9e088b577c9b3f5a3ff0fe77ec886` + /// 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. /// - /// This should also enough for most reasonable file names in the wild. + /// With 120 characters, we're settled well for basically all realistic file names + /// encountered in the wild. const max_file_name_len = 120; + /// 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 { - /// This is the file system which the os has bootet from. + /// The filesystem the OS booted from. item system = 0; - /// the filesystem isn't valid. - item invalid = 0xFFFFFFFF; - /// All other ids are unique file systems. ... } - bitstruct FileAttributes : u16 { - field directory: bool; - reserve u15 = 0; - } + /// Enumerates all currently available filesystems. + syscall enumerate_filesystems { + /// If not `null`, will receive filesystem ids. + in list: ?[]FileSystemId; - enum FileAccess : u8 { - item read_only = 0; - item write_only = 1; - item read_write = 2; - } + /// The number of ids written to `list`, or the total count if `list` is `null`. + out count: usize; - enum FileMode : u8 { - /// opens file when it exists on disk - item open_existing = 0; + error SystemResources; + } - /// creates file when it does not exist, or opens the file without truncation. - item open_always = 1; + /// Finds a filesystem by name. + /// + /// NOTE: Name matching is case-sensitive. + /// + /// NOTE: The passed `name` may include a trailing `:`. + syscall find_filesystem { + in name: str; - /// creates file when there is no file with that name - item create_new = 2; + out id: FileSystemId; - /// creates file when it does not exist, or opens the file and truncates it to zero length - item create_always = 3; + /// No filesystem exists with the given name. + error NotFound; } struct FileSystemInfo { - /// system-unique id of this file system + /// System-unique id of this file system field id: FileSystemId; - /// binary infos about the file system + + /// Compressed infos about the file system field flags: Flags; - /// user addressable file system identifier ('USB0', ...) + + /// User-addressable file system identifier (e.g. `SYS`, `USB0`, ...). + /// + /// Encoding: + /// - UTF-8 + /// - NUL-padded (first NUL determines length, otherwise full array). field name: [8]u8; //? TODO: Use max_fs_name_len instead of 8 - /// string identifier of a file system driver ('FAT32', ...) + /// 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: [32]u8; //? TODO: USe max_fs_type_len instead of 32 bitstruct Flags : u16 { - /// is the system boot disk + /// This is the system boot filesystem. field system: bool; - /// the file system can be removed by the user + /// The file system can be removed by the user. field removable: bool; - /// the file system is mounted as read-only - field read_only: bool; + /// The filesystem is immutable and cannot be modified. + field immutable: bool; reserve u13 = 0; } } - 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.DateTime; - field modified_date: datetime.DateTime; - } - - /// A directory is a group of files and other directories in a file system. - resource Directory { } - - /// A file is a handle to a binary data storage that is stored - /// in a file system. - /// - /// Files are byte-addressed. - resource File { } - - /// A file system location is a fusion of a directory handle with - /// an associated relative path. - /// - /// 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 { } + /// Queries information about a filesystem. + syscall get_filesystem_info { + in fs_id: FileSystemId; + out info: FileSystemInfo; - //? TODO: Add operations for locations + /// The given filesystem id does not exist. + error InvalidFileSystem; - /// Finds a file system by name - syscall find_filesystem { - in name: str; - out id: FileSystemId; + error SystemResources; } - /// Flushes all open files to disk. - async_call Sync { - error IoError; - } + /// A directory is a group of files and other directories in a file system. + resource Directory { } - /// 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 { + /// Opens a directory relative to the root of a filesystem. + async_call Mount { in fs_id: FileSystemId; - out info: FileSystemInfo; - out next: FileSystemId; - error IoError; - error InvalidFileSystem; - } - /// opens a directory on a filesystem - async_call OpenDrive { - 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 does not exist. error InvalidFileSystem; + + /// The given `path` is syntactically invalid. error InvalidPath; + + /// The requested entry exists but is not a directory. error NotADir; + error SystemResources; } - /// opens a directory relative to the given dir handle. + /// 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; - error IoError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NotADir; + + /// 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; } - /// closes the directory handle - async_call CloseDir { - in dir: Directory; - error InvalidHandle; + /// Writes the name of a directory into `*name_out`. + /// + /// 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; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; } - /// A directory enumerator allows enumerating elements inside - /// of a directory. + /// Returns the filesystem id a directory resides on. /// - /// NOTE: A directory enumerator is not unwindable/resettable - /// as it's typically simpler to destroy the enumerator - /// and create a new one than maintaining stateful directory - /// content tracking. + /// 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; + } + + /// 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; + + /// `dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; + } + + /// 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: Dir; out enumerator: DirEnumerator; /// `dir` is not a valid directory resource. - error InvalidHandle; + error InvalidHandle; + + /// The underlying filesystem of `dir` was removed. + error Gone; error SystemResources; } - /// This operation returns the FileInfo for the next file or directory inside - /// the `enumerator`. + /// 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; - out info: FileInfo; + + /// 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 can be destroyed after this error is + /// NOTE: The `enumerator` resource should be destroyed after this error is /// returned as there is no way to unwind an enumerator resource. error EndOfDirectory; - error InvalidHandle; + /// `enumerator` is not a valid enumerator resource. + error InvalidHandle; - /// The enumerator failed to advance to the next item as the underlying - /// filesystem failed. - error IoError; + /// The underlying filesystem of `enumerator` was removed. + error Gone; + + /// The underlying storage subsystem had an I/O failure. + error IoError; error SystemResources; } - /// deletes a file or directory by the given path. + /// 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; + + /// The path relative to `dir`, points to the file system entry that shall + /// be deleted. in path: str; + + /// 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; - error IoError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; + + /// 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. If `path` contains subdirectories, all - /// directories are created. + /// 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; - out new_dir: Directory; - error IoError; - error Exists; - error InvalidHandle; - error InvalidPath; + + /// 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; } - /// returns the type of the file/dir at path, also adds size and modification dates + /// Queries a filesystem entry by path. async_call StatEntry { in dir: Directory; in path: str; out info: FileInfo; - error IoError; - error FileNotFound; - error InvalidHandle; - error InvalidPath; + + /// 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; + + error SystemResources; } - /// renames a file inside the same file system. + /// 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 { - in src_dir: Directory; + /// 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; - error IoError; - error Exists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; + /// 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; + + /// `src_dir` is not a valid directory resource. + error InvalidHandle; + + /// The underlying filesystem of `src_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 a file or directory between two unrelated directories. Can also move between different file systems. - /// NOTE: This syscall might copy the data. + + /// 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; - error IoError; - error Exists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NoSpaceLeft; + + /// 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 a file or directory between two unrelated directories. Can also move between different file systems. + /// 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; - error IoError; - error Exists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NoSpaceLeft; + + /// 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 from the given directory. + /// 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; - error IoError; - error FileAlreadyExists; - error FileNotFound; - error InvalidHandle; - error InvalidPath; - error NoSpaceLeft; - error SystemResources; - error WriteProtected; - } - /// closes the handle and flushes the file. - async_call CloseFile { - in file: File; - error IoError; - error InvalidHandle; - error SystemResources; + /// 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; } - /// makes sure this file is safely stored to mass storage device - async_call FlushFile { - in file: File; - error IoError; - error InvalidHandle; - 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; } - /// directly reads data from a given offset into the file. no streaming API to the kernel + /// 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; - error IoError; - error InvalidHandle; + + /// 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; } - /// directly writes data to a given offset into the file. no streaming API to the kernel + /// 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; - error IoError; - error InvalidHandle; - error NoSpaceLeft; - error SystemResources; - error WriteProtected; + + /// 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; } - /// allows us to get the current size of the file, modification dates, and so on + /// Queries information about an opened file. async_call StatFile { in file: File; out info: FileInfo; - error IoError; - error InvalidHandle; - error SystemResources; + + /// 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 the file to the given length in bytes. Can be also used to truncate a file to zero length. + /// 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; - error IoError; - error InvalidHandle; - error NoSpaceLeft; - error SystemResources; + + /// 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; + + /// 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; } } From 9ecd8612a10abca1b0ab998486e1fcfbe796db7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 10 Feb 2026 07:27:38 +0100 Subject: [PATCH 24/42] Refines the fs namespace, adds vibecoded version of service in update.abi (needs proper refinement still) --- src/abi/src/ashet.abi | 39 ++++--- src/abi/src/update.abi | 240 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 13 deletions(-) create mode 100644 src/abi/src/update.abi diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 5d8a1d03..07e03129 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -3837,8 +3837,6 @@ namespace fs { /// The number of ids written to `list`, or the total count if `list` is `null`. out count: usize; - - error SystemResources; } /// Finds a filesystem by name. @@ -3853,6 +3851,9 @@ namespace fs { /// No filesystem exists with the given name. error NotFound; + + /// `name` does not conform to the name rules for filesystems. + error InvalidName; } struct FileSystemInfo { @@ -3867,14 +3868,14 @@ namespace fs { /// Encoding: /// - UTF-8 /// - NUL-padded (first NUL determines length, otherwise full array). - field name: [8]u8; //? TODO: Use max_fs_name_len instead of 8 + 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: [32]u8; //? TODO: USe max_fs_type_len instead of 32 + field filesystem: [max_fs_type_len]u8; bitstruct Flags : u16 { /// This is the system boot filesystem. @@ -3901,6 +3902,8 @@ namespace fs { 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 { } @@ -3921,7 +3924,7 @@ namespace fs { /// The requested entry does not exist. error FileNotFound; - /// The given filesystem id does not exist. + /// The given filesystem id did not exist when scheduling the operation. error InvalidFileSystem; /// The given `path` is syntactically invalid. @@ -3930,6 +3933,10 @@ namespace fs { /// 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; } @@ -4077,7 +4084,7 @@ namespace fs { /// Creates a directory enumerator for `dir`. syscall create_enumerator { - in dir: Dir; + in dir: Directory; out enumerator: DirEnumerator; @@ -4186,7 +4193,7 @@ namespace fs { /// /// 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; + out new_dir: ?Directory; /// The underlying storage subsystem had an I/O failure. error IoError; @@ -4236,6 +4243,9 @@ namespace fs { /// The given `path` is syntactically invalid. error InvalidPath; + /// A path traversal expected a directory, but found a non-directory entry. + error NotADir; + error SystemResources; } @@ -4267,10 +4277,10 @@ namespace fs { /// The source entry does not exist. error FileNotFound; - /// `src_dir` is not a valid directory resource. + /// `dir` is not a valid directory resource. error InvalidHandle; - /// The underlying filesystem of `src_dir` was removed. + /// The underlying filesystem of `dir` was removed. error Gone; /// A passed path is syntactically invalid. @@ -4511,8 +4521,8 @@ namespace fs { /// 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. + /// 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. @@ -4547,8 +4557,8 @@ namespace fs { /// 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. + /// 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. @@ -4780,6 +4790,9 @@ namespace fs { /// 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; diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi new file mode 100644 index 00000000..b3fcfe20 --- /dev/null +++ b/src/abi/src/update.abi @@ -0,0 +1,240 @@ +/// 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**: Services 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 services are automatically +/// bound to the receiver's process. +/// - **Context Switching**: The kernel switches the "Resource Context" of the executing thread +/// to the Service's owning process during the execution of the handler. +namespace service { + resource Service { } + + /// A 128-bit Universally Unique Identifier for an interface. + struct UUID { + field bytes: [16]u8; + } + + /// Defines the type of a parameter or return value. + enum ArgType : u2 { + /// A raw integer, pointer, or boolean. + /// The kernel passes this value through unmodified. + item raw = 0; + + /// A system resource handle. + /// + /// MARSHALLING rules: + /// - **Caller -> Service**: Kernel validates the caller owns the handle, then creates + /// a temporary `at_least_weak` binding for the Service process. + /// - **Service -> Caller**: Kernel validates the Service owns the handle, then creates + /// a permanent `strong` binding for the Caller process (transferring ownership/access). + item resource = 1; + + /// The argument slot is unused. + item none = 2; + } + + /// Describes the signature of a single service function. + /// + /// The signature supports up to 8 inputs and 8 outputs. + bitstruct FunctionSignature : u64 { + /// The number of input arguments (0..8). + field argc_in: u4; + + /// The number of output values (0..8). + field argc_out: u4; + + /// The types of the 8 input arguments (packed 2 bits each). + /// Index 0 is the LSB. + field inputs: u16; + + /// The types of the 8 output values (packed 2 bits each). + /// Index 0 is the LSB. + field outputs: u16; + + reserve u24 = 0; + } + + /// Describes a full service interface. + struct InterfaceDescriptor { + /// The unique contract identifier. + field uuid: UUID; + + /// Pointer to an array of signatures. + field signatures: []const FunctionSignature; + } + + /// A handle used by the service to identify a pending asynchronous request. + enum RequestHandle : u32 { ... } + + /// The signature of the asynchronous request handler function registered by the service. + /// + /// **Parameters:** + /// 1. `context`: The opaque pointer registered with the service. + /// 2. `req`: The handle to the incoming request. + /// 3. `func_index`: The index of the function being called. + /// 4. `args`: Pointer to the 8 input arguments provided by the caller. + /// + /// **Behavior:** + /// The handler should store the `req` and `args` (if needed) and return immediately. + /// The operation is completed later via `complete_request`. + typedef AsyncHandler = fnptr(?*anyopaque, RequestHandle, u16, *const [8]usize) void; + + /// Registers a new service instance in the system. + /// + /// NOTE: A service may provide `vtable` (for sync calls), `async_handler` (for async calls), + /// or both. If a method is not supported by the provided handlers, the kernel returns + /// `Unsupported` to the caller. + syscall register { + /// The interface contract this service implements. + in interface: InterfaceDescriptor; + + /// The synchronous implementation functions (vtable). + /// If `null`, synchronous `invoke` calls will fail. + in vtable: ?[]const anyfnptr; + + /// The asynchronous request handler. + /// If `null`, `async_call Invoke` will fail. + in async_handler: ?AsyncHandler; + + /// The opaque context pointer ("this") passed to the functions. + in context: ?*anyopaque; + + /// A human-readable name for debugging/enumeration. + in name: str; + + /// The created service resource. + out svc: Service; + + error SystemResources; + } + + /// Invokes a service function synchronously. + /// + /// **Execution Flow:** + /// 1. Kernel validates arguments against signature. + /// 2. Kernel context-switches to Service process. + /// 3. Kernel marshals input resources. + /// 4. Kernel calls `vtable[func_index]`. + /// 5. Kernel marshals output resources. + /// 6. Kernel context-switches back. + syscall invoke { + in svc: Service; + in func_index: u16; + in args: [8]usize; + + out results: [8]usize; + + error InvalidHandle; + error InvalidFunction; + error InvalidArg; + + /// The service does not support synchronous invocation. + error Unsupported; + } + + /// Invokes a service function asynchronously. + /// + /// **Execution Flow:** + /// 1. Kernel validates arguments against signature. + /// 2. Kernel allocates an internal Request State. + /// 3. Kernel context-switches to Service process (temporarily). + /// 4. Kernel marshals input resources. + /// 5. Kernel calls `async_handler`. + /// 6. Kernel context-switches back and returns the ARC to the caller. + async_call Invoke { + in svc: Service; + in func_index: u16; + in args: [8]usize; + + /// The results of the operation. + /// Filled by the kernel when the service calls `complete_request`. + out results: [8]usize; + + error InvalidHandle; + error InvalidFunction; + error InvalidArg; + + /// The service does not support asynchronous invocation. + error Unsupported; + + /// The service's pending request queue is full. + error SystemResources; + } + + /// Completes a pending asynchronous request (called by the Service). + /// + /// This consumes the `req` handle and wakes the caller (completing their ARC). + syscall complete_request { + /// The request handle passed to `AsyncHandler`. + in req: RequestHandle; + + /// The return values/resources. + /// + /// NOTE: Resources in this array will be marshalled to the caller. + in results: [8]usize; + + /// `req` is not a valid pending request. + error InvalidHandle; + } + + /// Rejects/Fails a pending asynchronous request (called by the Service). + /// + /// This completes the caller's ARC with a generic `RequestFailed` error (or similar). + syscall fail_request { + in req: RequestHandle; + + /// `req` is not a valid pending request. + error InvalidHandle; + } +} + + +//? Okay, so some comments to clarify what i want to rework: + +//? first of all, the kernel is actually able to stack-switch the current's thread resource context, which is already heavily used in the GUI module. + +//? So the "DLL" claim is not quite correct here. but also not fully wrong :P + +//? what i want to change is: + +//? make functions "generic" on the API surface, and add the ability to auto-transition between caller and callee, and, even better: automatically bind resources to the callee if necessary. + +//? the idea here is that you define a "signature" structure, both for query/discovery but also for registration. + +//? this signature is composed of: +//? - UUID +//? - Number of functions +//? - Parameter count of functions +//? - Parameter types (raw, resource) + +//? The number of generic parameters will be limited from 0..8 (inclusive) "usize" values. + +//? Each function also takes a ctx pointer. The caller invokes the function with the resource itself as a parameter. The kernel will then proxy and call the actual function with a service-bound context value. + +//? A usize might be interpreted as a resource handle. + +//? This allows the following things: +//? - way more safety against programming mistakes (good, it's not security) +//? - kernel can "thunk" or otherwise replace the functions as it knows the physical signature +//? - kernel can transfer resource ownerships on call +//? - kernel can patch thread association on call + +//? this should actually resolve most of the woes we have with the current design + +//? You know what we both totally ignored? supporting our own overlapped operations in the service! + +//? We can have "syscall invoke" (synchronous) and "async_call Invoke" (overlapped) and the called process must explicitly complete the operarion with "service.complete_invoke" or something. + +//? the async ops are even simpler: + +//? we just pass a number to the async op, and the service can register a "schedule" fnptr in the "register" syscall. as soon as that fn is available, the service can receive async operations which it can complete. + +//? signature has 8 ins, 8 outs. (we need at least one out anyways for sync calls as well!) + +//? damn, that sounds funky. \ No newline at end of file From 20505ddfee1e1dd4d0868cab96d5b045f60831b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 10 Feb 2026 09:44:49 +0100 Subject: [PATCH 25/42] Downgrades vscode package version requirement so it can be used for code-server. --- vendor/ashet-os-vscode/package-lock.json | 2 +- vendor/ashet-os-vscode/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 From afe0d7577881bd9f25fa899507c59186334ac941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 10 Feb 2026 09:48:43 +0100 Subject: [PATCH 26/42] Heavily reworks service namespace to be a really good match for Ashet OS' design --- src/abi/src/ashet.abi | 350 ++++++++++++++++++++++++++++++++++++++--- src/abi/src/update.abi | 240 ---------------------------- 2 files changed, 327 insertions(+), 263 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 07e03129..b9a12a6b 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -6102,48 +6102,352 @@ namespace gui { } } -//? TODO: Review this namespace. +/// 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 { - resource 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 signature of a function or overlapped operation. + /// + /// NOTE: For function signatures, `outputs[1..8]` 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, `input[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: Only `outputs[0]` may be set for functions. Overlapped operations + /// can use all eight values. + field outputs: [8]MarshalType; + + /// 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: + /// - **Caller -> Interface**: Kernel validates the caller owns the handle, then creates + /// a `at_least_weak` binding for the Interface process. + /// - **Interface -> Caller**: Kernel validates the Interface owns the handle, then creates + /// a permanent `strong` binding for the caller process (transferring ownership/access). + item resource = 3; + } + } + + /// A handle used by the interface to identify a pending asynchronous request. + enum RequestHandle : u32 { ... } + + /// The signature of the asynchronous request handler function registered by the interface. + /// + /// **Parameters:** + /// 1. `context`: The opaque pointer associated with the interface. + /// 2. `request`: The handle to the incoming request. + /// 3. `function`: The index of the function being called. + /// 4. `arguments`: Pointer to the 8 input arguments provided by the caller. + /// + /// **Behavior:** + /// The handler should store the `req` and `args` (if needed) and return immediately. + /// The operation is completed later via `complete_request`. + typedef AsyncHandler = fnptr(context: ?*anyopaque, request: RequestHandle, function: u8, arguments: *const [8]usize) void; + + /// The signature of a synchronous function call registered in an interface. + /// + /// **Parameters:** + /// 1. `context`: The opaque pointer associated with the interface. + /// 2. `args`: 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. + typedef Function = fnptr(context: ?*anyopaque, args: *const [8]usize) usize; - /// 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. + /// 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. + /// + /// This UUID defines the contract this interface implements. in uuid: *const UUID; - in funcs: []const anyfnptr; + + /// A human-readable name for enumeration and debugging. in name: str; - out svc: Service; - error AlreadyRegistered; + + /// Defines the signatures and count of synchronous function calls in the interface. + /// + /// NOTE: The kernel will create an internal copy of this array, so userland + /// can reuse the memory freely after this call. + field sync_signatures: []const FunctionSignature; + + /// Defines the signatures and count of overlapped operations in the interface. + /// + /// NOTE: The kernel will create an internal copy of this array, so userland + /// can reuse the memory freely after this call. + field async_signatures: []const FunctionSignature; + + /// The opaque context pointer ("this") passed to the functions in `vtable` and the `async_handler`. + /// + /// 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; + + /// The synchronous implementation functions (vtable). + /// + /// NOTE: Must have the same number of elements as `sync_signatures`. + /// + /// NOTE: The kernel will create an internal copy of this array, so userland + /// can reuse the memory freely after this call. + /// + /// NOTE: If no synchronous functions are supported, a zero-lengthed array can be passed. + in vtable: []const Function; + + /// The asynchronous request handler. + /// + /// NOTE: All synchronous requests go through the same function handler, and + /// dispatch must happen in userland. + /// + /// The kernel ensures that argument marshalling will be properly performed, + /// and function will never be out of range for the interface. + /// + /// NOTE: May be `null` if, and only if, `async_signatures.len == 0`. + in async_handler: ?AsyncHandler; + + /// The created interface resource. + out interface: Interface; + error SystemResources; } - /// Enumerates all registered services. - syscall enumerate { - in uuid: *const UUID; - in services: ?[]Service; - out count: usize; + /// Returns the UUID of the interface. + syscall get_name { + in interface: Interface; + out uuid: UUID; + + /// `interface` is not a valid interface resource. + error InvalidHandle; } - /// Returns the name of the service. + /// Returns the name of the interface. syscall get_name { - in svc: Service; + in interface: Interface; in name_buf: ?[]u8; + + /// 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; + + /// `interface` is not a valid interface resource. error InvalidHandle; } - /// Returns the process that created this service. - syscall get_process { - in svc: Service; - out process: process.Process; + /// 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. + syscall register { + /// The interface that shall be published. + in interface: Interface; + + /// `interface` is not a valid interface resource. error InvalidHandle; + + error SystemResources; } - /// Returns the functions registerd by the service. - syscall get_functions { - in svc: Service; - in funcs: ?[]anyfnptr; - out count: usize; + /// 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; + + /// 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; + + /// Number of elements written to `services` or total number of registered + /// interfaces. + out count: usize; + } + + /// 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. + in arguments: [8]usize; + + /// The return value of the function. + out result: usize; + + /// `interface` is not a valid interface resource. + error InvalidHandle; + + /// `function` does not exist. + error InvalidFunction; + + /// 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; + + error SystemResources; + } + + /// 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. + in arguments: [8]usize; + + /// The results of the operation. + /// + /// NOTE: Filled by the kernel when the interface implementor calls `complete_request`. + out results: [8]usize; + + /// `interface` is not a valid interface resource. + error InvalidHandle; + + /// `operation` does not exist. + error InvalidFunction; + + /// 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; + + /// The operation was failed by a call to `fail_request`. + error RequestFailed; + + error SystemResources; + } + + /// Completes a pending asynchronous request (called by the interface implementor). + /// + /// NOTE: This consumes the `request` token and wakes the caller (completing their ARC). + syscall complete_request { + /// The request handle passed to `AsyncHandler`. + in request: RequestHandle; + + /// The return values/resources. + /// + /// NOTE: Resources in this array will be marshalled to the caller as specified + /// in the operation signature. + in results: [8]usize; + + /// `request` is not a valid pending request. + error InvalidHandle; + } + + /// 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 handle passed to `AsyncHandler`. + in request: RequestHandle; + + /// `request` is not a valid pending request. error InvalidHandle; } } diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index b3fcfe20..e69de29b 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -1,240 +0,0 @@ -/// 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**: Services 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 services are automatically -/// bound to the receiver's process. -/// - **Context Switching**: The kernel switches the "Resource Context" of the executing thread -/// to the Service's owning process during the execution of the handler. -namespace service { - resource Service { } - - /// A 128-bit Universally Unique Identifier for an interface. - struct UUID { - field bytes: [16]u8; - } - - /// Defines the type of a parameter or return value. - enum ArgType : u2 { - /// A raw integer, pointer, or boolean. - /// The kernel passes this value through unmodified. - item raw = 0; - - /// A system resource handle. - /// - /// MARSHALLING rules: - /// - **Caller -> Service**: Kernel validates the caller owns the handle, then creates - /// a temporary `at_least_weak` binding for the Service process. - /// - **Service -> Caller**: Kernel validates the Service owns the handle, then creates - /// a permanent `strong` binding for the Caller process (transferring ownership/access). - item resource = 1; - - /// The argument slot is unused. - item none = 2; - } - - /// Describes the signature of a single service function. - /// - /// The signature supports up to 8 inputs and 8 outputs. - bitstruct FunctionSignature : u64 { - /// The number of input arguments (0..8). - field argc_in: u4; - - /// The number of output values (0..8). - field argc_out: u4; - - /// The types of the 8 input arguments (packed 2 bits each). - /// Index 0 is the LSB. - field inputs: u16; - - /// The types of the 8 output values (packed 2 bits each). - /// Index 0 is the LSB. - field outputs: u16; - - reserve u24 = 0; - } - - /// Describes a full service interface. - struct InterfaceDescriptor { - /// The unique contract identifier. - field uuid: UUID; - - /// Pointer to an array of signatures. - field signatures: []const FunctionSignature; - } - - /// A handle used by the service to identify a pending asynchronous request. - enum RequestHandle : u32 { ... } - - /// The signature of the asynchronous request handler function registered by the service. - /// - /// **Parameters:** - /// 1. `context`: The opaque pointer registered with the service. - /// 2. `req`: The handle to the incoming request. - /// 3. `func_index`: The index of the function being called. - /// 4. `args`: Pointer to the 8 input arguments provided by the caller. - /// - /// **Behavior:** - /// The handler should store the `req` and `args` (if needed) and return immediately. - /// The operation is completed later via `complete_request`. - typedef AsyncHandler = fnptr(?*anyopaque, RequestHandle, u16, *const [8]usize) void; - - /// Registers a new service instance in the system. - /// - /// NOTE: A service may provide `vtable` (for sync calls), `async_handler` (for async calls), - /// or both. If a method is not supported by the provided handlers, the kernel returns - /// `Unsupported` to the caller. - syscall register { - /// The interface contract this service implements. - in interface: InterfaceDescriptor; - - /// The synchronous implementation functions (vtable). - /// If `null`, synchronous `invoke` calls will fail. - in vtable: ?[]const anyfnptr; - - /// The asynchronous request handler. - /// If `null`, `async_call Invoke` will fail. - in async_handler: ?AsyncHandler; - - /// The opaque context pointer ("this") passed to the functions. - in context: ?*anyopaque; - - /// A human-readable name for debugging/enumeration. - in name: str; - - /// The created service resource. - out svc: Service; - - error SystemResources; - } - - /// Invokes a service function synchronously. - /// - /// **Execution Flow:** - /// 1. Kernel validates arguments against signature. - /// 2. Kernel context-switches to Service process. - /// 3. Kernel marshals input resources. - /// 4. Kernel calls `vtable[func_index]`. - /// 5. Kernel marshals output resources. - /// 6. Kernel context-switches back. - syscall invoke { - in svc: Service; - in func_index: u16; - in args: [8]usize; - - out results: [8]usize; - - error InvalidHandle; - error InvalidFunction; - error InvalidArg; - - /// The service does not support synchronous invocation. - error Unsupported; - } - - /// Invokes a service function asynchronously. - /// - /// **Execution Flow:** - /// 1. Kernel validates arguments against signature. - /// 2. Kernel allocates an internal Request State. - /// 3. Kernel context-switches to Service process (temporarily). - /// 4. Kernel marshals input resources. - /// 5. Kernel calls `async_handler`. - /// 6. Kernel context-switches back and returns the ARC to the caller. - async_call Invoke { - in svc: Service; - in func_index: u16; - in args: [8]usize; - - /// The results of the operation. - /// Filled by the kernel when the service calls `complete_request`. - out results: [8]usize; - - error InvalidHandle; - error InvalidFunction; - error InvalidArg; - - /// The service does not support asynchronous invocation. - error Unsupported; - - /// The service's pending request queue is full. - error SystemResources; - } - - /// Completes a pending asynchronous request (called by the Service). - /// - /// This consumes the `req` handle and wakes the caller (completing their ARC). - syscall complete_request { - /// The request handle passed to `AsyncHandler`. - in req: RequestHandle; - - /// The return values/resources. - /// - /// NOTE: Resources in this array will be marshalled to the caller. - in results: [8]usize; - - /// `req` is not a valid pending request. - error InvalidHandle; - } - - /// Rejects/Fails a pending asynchronous request (called by the Service). - /// - /// This completes the caller's ARC with a generic `RequestFailed` error (or similar). - syscall fail_request { - in req: RequestHandle; - - /// `req` is not a valid pending request. - error InvalidHandle; - } -} - - -//? Okay, so some comments to clarify what i want to rework: - -//? first of all, the kernel is actually able to stack-switch the current's thread resource context, which is already heavily used in the GUI module. - -//? So the "DLL" claim is not quite correct here. but also not fully wrong :P - -//? what i want to change is: - -//? make functions "generic" on the API surface, and add the ability to auto-transition between caller and callee, and, even better: automatically bind resources to the callee if necessary. - -//? the idea here is that you define a "signature" structure, both for query/discovery but also for registration. - -//? this signature is composed of: -//? - UUID -//? - Number of functions -//? - Parameter count of functions -//? - Parameter types (raw, resource) - -//? The number of generic parameters will be limited from 0..8 (inclusive) "usize" values. - -//? Each function also takes a ctx pointer. The caller invokes the function with the resource itself as a parameter. The kernel will then proxy and call the actual function with a service-bound context value. - -//? A usize might be interpreted as a resource handle. - -//? This allows the following things: -//? - way more safety against programming mistakes (good, it's not security) -//? - kernel can "thunk" or otherwise replace the functions as it knows the physical signature -//? - kernel can transfer resource ownerships on call -//? - kernel can patch thread association on call - -//? this should actually resolve most of the woes we have with the current design - -//? You know what we both totally ignored? supporting our own overlapped operations in the service! - -//? We can have "syscall invoke" (synchronous) and "async_call Invoke" (overlapped) and the called process must explicitly complete the operarion with "service.complete_invoke" or something. - -//? the async ops are even simpler: - -//? we just pass a number to the async op, and the service can register a "schedule" fnptr in the "register" syscall. as soon as that fn is available, the service can receive async operations which it can complete. - -//? signature has 8 ins, 8 outs. (we need at least one out anyways for sync calls as well!) - -//? damn, that sounds funky. \ No newline at end of file From e7ecdf6c02270ec4fea75a8958709c5b862ac5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Tue, 10 Feb 2026 13:15:37 +0100 Subject: [PATCH 27/42] Clarifies more parts of service namespace, adds proper cancellation support. --- src/abi/src/ashet.abi | 210 ++++++++++++++++++++++++++++++++---------- 1 file changed, 161 insertions(+), 49 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index b9a12a6b..81122383 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -6142,10 +6142,49 @@ namespace service { /// 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..8]` must be set to + /// 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 @@ -6153,7 +6192,7 @@ namespace service { /// `MarshalType.unused`. bitstruct FunctionSignature : u32 { /// Defines the number and type of the input arguments. - /// `inputs[0]` is the first argument, `input[7]` is the eighth argument. + /// `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. @@ -6162,59 +6201,65 @@ namespace service { /// NOTE: Only `outputs[0]` may be set for functions. Overlapped operations /// can use all eight values. field outputs: [8]MarshalType; - - /// 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: - /// - **Caller -> Interface**: Kernel validates the caller owns the handle, then creates - /// a `at_least_weak` binding for the Interface process. - /// - **Interface -> Caller**: Kernel validates the Interface owns the handle, then creates - /// a permanent `strong` binding for the caller process (transferring ownership/access). - item resource = 3; - } } - /// A handle used by the interface to identify a pending asynchronous request. - enum RequestHandle : u32 { ... } + /// 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 { ... } - /// The signature of the asynchronous request handler function registered by the interface. + /// 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 handle to the incoming request. - /// 3. `function`: The index of the function being called. + /// 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 `args` (if needed) and return immediately. + /// The handler should store the `req` and the values inside `args` (if needed) and return immediately. /// The operation is completed later via `complete_request`. - typedef AsyncHandler = fnptr(context: ?*anyopaque, request: RequestHandle, function: u8, arguments: *const [8]usize) void; + /// + /// 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; + + /// 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; - /// The signature of a synchronous function call registered in an interface. + /// The signature of a synchronous function call registered by an interface. /// /// **Parameters:** - /// 1. `context`: The opaque pointer associated with the interface. - /// 2. `args`: Pointer to the 8 input arguments provided by the caller. + /// 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. - typedef Function = fnptr(context: ?*anyopaque, args: *const [8]usize) usize; + /// + /// 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; /// Creates a new interface that can be invoked by other processes. /// @@ -6227,6 +6272,8 @@ namespace service { /// The unique identifier of the interface. /// /// This UUID defines the contract this interface implements. + /// + /// NOTE: The kernel copies the UUID object internally. in uuid: *const UUID; /// A human-readable name for enumeration and debugging. @@ -6236,13 +6283,13 @@ namespace service { /// /// NOTE: The kernel will create an internal copy of this array, so userland /// can reuse the memory freely after this call. - field sync_signatures: []const FunctionSignature; + in sync_signatures: []const FunctionSignature; /// Defines the signatures and count of overlapped operations in the interface. /// /// NOTE: The kernel will create an internal copy of this array, so userland /// can reuse the memory freely after this call. - field async_signatures: []const FunctionSignature; + in async_signatures: []const FunctionSignature; /// The opaque context pointer ("this") passed to the functions in `vtable` and the `async_handler`. /// @@ -6257,13 +6304,11 @@ namespace service { /// /// NOTE: The kernel will create an internal copy of this array, so userland /// can reuse the memory freely after this call. - /// - /// NOTE: If no synchronous functions are supported, a zero-lengthed array can be passed. in vtable: []const Function; /// The asynchronous request handler. /// - /// NOTE: All synchronous requests go through the same function handler, and + /// NOTE: All asynchronous requests go through the same function handler, and /// dispatch must happen in userland. /// /// The kernel ensures that argument marshalling will be properly performed, @@ -6272,14 +6317,43 @@ namespace service { /// NOTE: May be `null` if, and only if, `async_signatures.len == 0`. in async_handler: ?AsyncHandler; + /// The asynchronous cancellation handler. + /// + /// NOTE: All cancellation requests go through the same function handler, and + /// dispatch must happen in userland. + /// + /// NOTE: May be `null` even if `async_signatures.len > 0`. + /// + /// NOTE: If `null`, the kernel just invalidates the `RequestToken` on cancellation + /// and will not allow completion of the request. + /// + /// The userland process may still perform unnecessary work. + in cancel_handler: ?CancelHandler; + /// The created interface resource. out interface: Interface; + /// 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; + + /// 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; } /// Returns the UUID of the interface. - syscall get_name { + syscall get_interface_uuid { in interface: Interface; out uuid: UUID; @@ -6288,7 +6362,7 @@ namespace service { } /// Returns the name of the interface. - syscall get_name { + syscall get_interface_name { in interface: Interface; in name_buf: ?[]u8; @@ -6306,6 +6380,8 @@ namespace service { /// /// 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; @@ -6332,6 +6408,8 @@ namespace service { /// Number of elements written to `services` or total number of registered /// interfaces. out count: usize; + + error SystemResources; } /// Invokes an interface function synchronously. @@ -6354,9 +6432,16 @@ namespace service { /// 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; /// 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; /// `interface` is not a valid interface resource. @@ -6372,6 +6457,18 @@ namespace service { /// - 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; + error SystemResources; } @@ -6395,11 +6492,15 @@ namespace service { /// 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; /// 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; /// `interface` is not a valid interface resource. @@ -6425,17 +6526,28 @@ namespace service { /// /// NOTE: This consumes the `request` token and wakes the caller (completing their ARC). syscall complete_request { - /// The request handle passed to `AsyncHandler`. - in request: RequestHandle; + /// The request token passed to `AsyncHandler`. + in request: RequestToken; /// 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; /// `request` is not a valid pending request. error InvalidHandle; + + /// 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; + + error SystemResources; } /// Rejects/fails a pending asynchronous request (called by the interface implementor). @@ -6444,8 +6556,8 @@ namespace service { /// /// NOTE: This consumes the `request` token and wakes the caller (completing their ARC). syscall fail_request { - /// The request handle passed to `AsyncHandler`. - in request: RequestHandle; + /// The request token passed to `AsyncHandler`. + in request: RequestToken; /// `request` is not a valid pending request. error InvalidHandle; From 31ee7b02fec9b02fbf85b4b6d6bc5481081d24de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 11 Feb 2026 00:28:10 +0100 Subject: [PATCH 28/42] Some network thoughts --- src/abi/src/ashet.abi | 2 -- src/abi/src/update.abi | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 81122383..25700b55 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -3699,9 +3699,7 @@ namespace network { error SystemResources; error Timeout; } - } - } /// A file or directory on Ashet OS can be named with any legal UTF-8 sequence diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index e69de29b..db19bd66 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -0,0 +1,31 @@ + +//? Pending: namespace gui +//? Pending: namespace network +//? 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; } From 1eb7593598c1bae9979bc58d7ea93975920c8860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 11 Feb 2026 00:41:31 +0100 Subject: [PATCH 29/42] Adds a huge summary of what Ashet OS network wants to be. --- src/abi/src/ashet.abi | 33 ++++++++------ src/abi/src/update.abi | 98 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 25700b55..2ac59da4 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -370,6 +370,11 @@ namespace overlapped { /// 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; } @@ -6136,11 +6141,11 @@ namespace service { /// 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 { @@ -6184,8 +6189,8 @@ namespace service { /// /// 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 + /// + /// 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 { @@ -6205,7 +6210,7 @@ namespace service { /// /// NOTE: Request tokens are valid globally and may be passed between processes. enum RequestToken : u32 { ... } - + /// The signature of the asynchronous request handler function registered by an interface. /// /// **Parameters:** @@ -6268,7 +6273,7 @@ namespace service { /// published with `register`. syscall create { /// The unique identifier of the interface. - /// + /// /// This UUID defines the contract this interface implements. /// /// NOTE: The kernel copies the UUID object internally. @@ -6306,7 +6311,7 @@ namespace service { /// The asynchronous request handler. /// - /// NOTE: All asynchronous requests go through the same function handler, and + /// NOTE: All asynchronous requests go through the same function handler, and /// dispatch must happen in userland. /// /// The kernel ensures that argument marshalling will be properly performed, @@ -6317,7 +6322,7 @@ namespace service { /// The asynchronous cancellation handler. /// - /// NOTE: All cancellation requests go through the same function handler, and + /// NOTE: All cancellation requests go through the same function handler, and /// dispatch must happen in userland. /// /// NOTE: May be `null` even if `async_signatures.len > 0`. @@ -6421,12 +6426,12 @@ namespace service { /// 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. @@ -6458,7 +6463,7 @@ namespace service { /// 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 + /// - 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 @@ -6481,12 +6486,12 @@ namespace service { /// 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. @@ -6503,7 +6508,7 @@ namespace service { /// `interface` is not a valid interface resource. error InvalidHandle; - + /// `operation` does not exist. error InvalidFunction; diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index db19bd66..c95422a8 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -7,7 +7,7 @@ namespace link; enum Interface : u16 { any = 0, ... } -struct InterfaceDescription { /* type, name, vendor, bandwidth, mtu, mac, ... */ } +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 } @@ -29,3 +29,99 @@ 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. From 6272ff04c2856dba2aa826e9f483bd311bccae3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 11 Feb 2026 07:03:05 +0100 Subject: [PATCH 30/42] Adds AI vibed draft of network.link to update.abi --- src/abi/src/update.abi | 211 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index c95422a8..503dc537 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -125,3 +125,214 @@ Ashet OS networking model (current intent): - Resource teardown: - Destroying sockets cancels ops immediately, no protocol close. - Destroying interfaces yields error.Gone to dependents. + + +namespace network.link { + + /// Network interface identifier + enum Interface : u32 { + item any = 0; + ... + } + + /// Interface type / capabilities + enum InterfaceType : u8 { + item ethernet; + item loopback; + item virtual; + ... + } + + /// Subsystems controllable per interface + enum Subsystem : u32 { + item ipv4; + item ipv6; + item dhcp4; + item dhcp6; + ... + } + + struct InterfaceDescription { + field type: InterfaceType; + field name: []const u8; + field vendor: []const u8; + field max_bandwidth: u64; + field current_bandwidth: u64; + field enabled_subsystems: u32; // bitmask of Subsystem + } + + syscall enumerate_interfaces { + in list: ?[]Interface; + out count: usize; + error SystemResources; + } + + syscall get_description { + in interface: Interface; + out description: InterfaceDescription; + error InvalidHandle, Gone; + } + + syscall set_subsystems { + in interface: Interface; + in enable_mask: u32; + in disable_mask: u32; + error InvalidHandle, Gone, InvalidValue; + } + + syscall get_subsystems { + in interface: Interface; + out enabled_mask: u32; + error InvalidHandle, Gone; + } + + /// IP addressing + + enum AddressOrigin : u8 { + item manual; + item dhcp4; + item dhcp6; + item slaac; + item autoconfig; + ... + } + + struct AddressBinding { + field ip_type: IP_Type; + field address: IP; + field prefix_len: u8; + field origin: AddressOrigin; + field valid_until: clock.Absolute; + } + + syscall enumerate_addresses { + in interface: Interface; + in bindings: ?[]AddressBinding; + out count: usize; + error InvalidHandle, Gone, SystemResources; + } + + async_call AddAddress { + in interface: Interface; + in binding: AddressBinding; + error InvalidHandle, Gone, InvalidValue, SystemResources, IoError; + } + + async_call RemoveAddress { + in interface: Interface; + in binding: AddressBinding; + error InvalidHandle, Gone, InvalidValue, SystemResources, IoError; + } + + /// Routing + + enum RouteOrigin : u8 { + item manual; + item dhcp4; + item dhcp6; + item slaac; + item kernel; + ... + } + + struct Route { + field ip_type: IP_Type; + field destination: IP; + field prefix_len: u8; + field gateway: ?IP; + field interface: Interface; + field priority: u32; + field origin: RouteOrigin; + field valid_until: ?clock.Absolute; + } + + syscall enumerate_routes { + in routes: ?[]Route; + out count: usize; + error SystemResources; + } + + syscall add_route { + in route: Route; + error InvalidValue, SystemResources; + } + + syscall remove_route { + in route: Route; + error InvalidValue; + } + + /// DHCP control + + async_call RequestDhcp4Lease { + in interface: Interface; + error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; + } + + async_call RequestDhcp6Lease { + in interface: Interface; + error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; + } + + struct Dhcp4Info { + field address: IP; + field prefix_len: u8; + field router: ?IP; + field dns_servers: []IP; + field valid_until: clock.Absolute; + } + + struct Dhcp6Info { + field addresses: []IP; + field dns_servers: []IP; + field valid_until: clock.Absolute; + } + + syscall get_dhcp4_info { + in interface: Interface; + out info: Dhcp4Info; + error InvalidHandle, Gone, Unsupported; + } + + syscall get_dhcp6_info { + in interface: Interface; + out info: Dhcp6Info; + error InvalidHandle, Gone, Unsupported; + } + + /// Link state + + enum LinkState : u8 { + item down; + item up; + ... + } + + syscall get_link_state { + in interface: Interface; + out state: LinkState; + error InvalidHandle, Gone; + } + + async_call WaitForLinkState { + in interface: Interface; + in desired: ?LinkState; + error InvalidHandle, Gone, Cancelled; + } + + /// ICMP Ping + + async_call Ping { + in interface: ?Interface; + in target: IP; + in ttl: u16; + in timeout: clock.Duration; + in payload: bytebuf; + + out responder: IP; + out duration: clock.Duration; + out hops: u32; + + error InvalidHandle, Gone, Timeout, IcmpError, MissingRoute, IoError; + } +} From 9d0c6d09b34212ab845135797551c6a0a835d68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Wed, 11 Feb 2026 20:39:28 +0100 Subject: [PATCH 31/42] Starts to thoroughly work onn the new network namespace --- src/abi/src/ashet.abi | 10 ++ src/abi/src/network.abi | 356 ++++++++++++++++++++++++++++++++++++++++ src/abi/src/update.abi | 92 ----------- 3 files changed, 366 insertions(+), 92 deletions(-) create mode 100644 src/abi/src/network.abi diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 2ac59da4..80db7374 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1288,7 +1288,17 @@ namespace clock { /// 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; + ... } diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi new file mode 100644 index 00000000..36b76a97 --- /dev/null +++ b/src/abi/src/network.abi @@ -0,0 +1,356 @@ + +/// 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. + field type: Type; + + /// Union of the possible address types. + field addr: AnyAddr; + + enum Type : u8 { + item ipv4 = 0; + item ipv6 = 1; + } + + union AnyAddr { + /// Active when `IP.type == Type.ipv4`. + field v4: IPv4; + + /// Active when `IP.type == Type.ipv6`. + field v6: IPv6; + } +} + +/// An endpoint defines a connetion 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: Talk about kernel subsystems being enabled/disabled and what each subsystem +/// is supposed to to. +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: 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./8` + /// and `::1/128` by default. + /// TODO: Loopback also requires routing rules. + 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. + 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 interface. + item ethernet = 2; + + /// The interface is virtual and is controlled by + /// a software. + item virtual = 3; + } + + /// 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; + + reserve u28; + } + + struct InterfaceDescription { + /// The type of this network interface. + field type: InterfaceType; + + /// 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; + } + + /// 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. + 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: 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 with (`interface`, `binding.address`, `binding.prefix_len`). + /// 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 { + /// 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. + error InvalidValue; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + 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`, `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. + 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. + 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; + } +} diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index 503dc537..38827e41 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -129,100 +129,8 @@ Ashet OS networking model (current intent): namespace network.link { - /// Network interface identifier - enum Interface : u32 { - item any = 0; - ... - } - - /// Interface type / capabilities - enum InterfaceType : u8 { - item ethernet; - item loopback; - item virtual; - ... - } - - /// Subsystems controllable per interface - enum Subsystem : u32 { - item ipv4; - item ipv6; - item dhcp4; - item dhcp6; - ... - } - - struct InterfaceDescription { - field type: InterfaceType; - field name: []const u8; - field vendor: []const u8; - field max_bandwidth: u64; - field current_bandwidth: u64; - field enabled_subsystems: u32; // bitmask of Subsystem - } - - syscall enumerate_interfaces { - in list: ?[]Interface; - out count: usize; - error SystemResources; - } - - syscall get_description { - in interface: Interface; - out description: InterfaceDescription; - error InvalidHandle, Gone; - } - - syscall set_subsystems { - in interface: Interface; - in enable_mask: u32; - in disable_mask: u32; - error InvalidHandle, Gone, InvalidValue; - } - - syscall get_subsystems { - in interface: Interface; - out enabled_mask: u32; - error InvalidHandle, Gone; - } - /// IP addressing - enum AddressOrigin : u8 { - item manual; - item dhcp4; - item dhcp6; - item slaac; - item autoconfig; - ... - } - - struct AddressBinding { - field ip_type: IP_Type; - field address: IP; - field prefix_len: u8; - field origin: AddressOrigin; - field valid_until: clock.Absolute; - } - - syscall enumerate_addresses { - in interface: Interface; - in bindings: ?[]AddressBinding; - out count: usize; - error InvalidHandle, Gone, SystemResources; - } - - async_call AddAddress { - in interface: Interface; - in binding: AddressBinding; - error InvalidHandle, Gone, InvalidValue, SystemResources, IoError; - } - - async_call RemoveAddress { - in interface: Interface; - in binding: AddressBinding; - error InvalidHandle, Gone, InvalidValue, SystemResources, IoError; - } /// Routing From 345f6b94267f663db0e8b73ec1b0188bafc4dacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 12 Feb 2026 00:05:05 +0100 Subject: [PATCH 32/42] Improves network.link draft a lot and refines most state --- src/abi/src/ashet.abi | 2 +- src/abi/src/network.abi | 325 ++++++++++++++++++++++++++++++++++++++-- src/abi/src/update.abi | 83 +--------- 3 files changed, 322 insertions(+), 88 deletions(-) diff --git a/src/abi/src/ashet.abi b/src/abi/src/ashet.abi index 80db7374..fdfe3e13 100644 --- a/src/abi/src/ashet.abi +++ b/src/abi/src/ashet.abi @@ -1292,7 +1292,7 @@ namespace clock { 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. diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 36b76a97..1aefbc0a 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -42,12 +42,12 @@ struct IP { } } -/// An endpoint defines a connetion target for TCP and UDP connections. +/// 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. @@ -55,9 +55,13 @@ struct EndPoint { } /// +/// 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. +/// +/// TODO: Write how routing works in Ashet OS. +/// namespace link { /// Unique identifier of a network interface. /// @@ -65,6 +69,13 @@ namespace link { /// 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 { @@ -75,9 +86,11 @@ namespace link { /// 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./8` + /// NOTE: The loopback interface has the IP addresses `127.0.0.1/8` /// and `::1/128` by default. - /// TODO: Loopback also requires routing rules. + /// + /// 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 @@ -87,6 +100,9 @@ namespace link { /// 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; ... @@ -117,7 +133,7 @@ namespace link { field dhcp4: bool; field dhcp6: bool; - reserve u28; + reserve u28 = 0; } struct InterfaceDescription { @@ -246,6 +262,10 @@ namespace link { /// 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. @@ -259,6 +279,8 @@ namespace link { /// 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; } @@ -283,7 +305,11 @@ namespace link { /// Adds a new IP address to an interface. /// /// NOTE: This operation will implicitly add a new route to the routing - /// table with (`interface`, `binding.address`, `binding.prefix_len`). + /// 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 @@ -300,7 +326,7 @@ namespace link { /// The binding specification for the IP address. in binding: AddressBinding; - + /// `interface` is not a valid interface id (anymore). error InvalidInterface; @@ -313,6 +339,8 @@ namespace link { /// - `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. @@ -324,7 +352,7 @@ namespace link { /// Removes an address from the interface. /// /// NOTE: This operation will implicitly remove the connected route from the routing - /// table with the key (`interface`, `address`, `RouteOrigin.connected`). + /// 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 @@ -337,8 +365,11 @@ namespace link { /// /// 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; @@ -353,4 +384,280 @@ namespace link { 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; + + error SystemResources; + } + + /// Removes an existing route from the routing table. + /// + /// NOTE: Removing a non-existent route is idempotent and does nothing. + 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; + + /// There was an i/o error that lead to the failure of this operation. + error IoError; + } } diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index 38827e41..ea0fa75d 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -1,6 +1,5 @@ //? Pending: namespace gui -//? Pending: namespace network //? Pending: namespace io.serial //? Pending: namespace io.i2c @@ -132,53 +131,15 @@ namespace network.link { /// IP addressing - /// Routing - - enum RouteOrigin : u8 { - item manual; - item dhcp4; - item dhcp6; - item slaac; - item kernel; - ... - } - - struct Route { - field ip_type: IP_Type; - field destination: IP; - field prefix_len: u8; - field gateway: ?IP; - field interface: Interface; - field priority: u32; - field origin: RouteOrigin; - field valid_until: ?clock.Absolute; - } - - syscall enumerate_routes { - in routes: ?[]Route; - out count: usize; - error SystemResources; - } - - syscall add_route { - in route: Route; - error InvalidValue, SystemResources; - } - - syscall remove_route { - in route: Route; - error InvalidValue; - } - - /// DHCP control + //? DHCP control async_call RequestDhcp4Lease { - in interface: Interface; + in interface: InterfaceId; error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; } async_call RequestDhcp6Lease { - in interface: Interface; + in interface: InterfaceId; error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; } @@ -197,50 +158,16 @@ namespace network.link { } syscall get_dhcp4_info { - in interface: Interface; + in interface: InterfaceId; out info: Dhcp4Info; error InvalidHandle, Gone, Unsupported; } syscall get_dhcp6_info { - in interface: Interface; + in interface: InterfaceId; out info: Dhcp6Info; error InvalidHandle, Gone, Unsupported; } - /// Link state - - enum LinkState : u8 { - item down; - item up; - ... - } - - syscall get_link_state { - in interface: Interface; - out state: LinkState; - error InvalidHandle, Gone; - } - async_call WaitForLinkState { - in interface: Interface; - in desired: ?LinkState; - error InvalidHandle, Gone, Cancelled; - } - - /// ICMP Ping - - async_call Ping { - in interface: ?Interface; - in target: IP; - in ttl: u16; - in timeout: clock.Duration; - in payload: bytebuf; - - out responder: IP; - out duration: clock.Duration; - out hops: u32; - - error InvalidHandle, Gone, Timeout, IcmpError, MissingRoute, IoError; - } } From 4607a2e59f2b20f03e285a4b04913f692779b93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 12 Feb 2026 00:16:49 +0100 Subject: [PATCH 33/42] Adds notes about the IPv4 and IPv6 subsystem. --- src/abi/src/network.abi | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 1aefbc0a..38b2748f 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -60,6 +60,29 @@ struct EndPoint { /// 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 +/// +/// DHCPv6: +/// TODO: Write subsystem docs +/// +/// /// TODO: Write how routing works in Ashet OS. /// namespace link { @@ -346,6 +369,9 @@ namespace link { /// 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; } @@ -357,6 +383,13 @@ namespace link { /// 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; @@ -514,12 +547,20 @@ namespace link { /// `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. /// @@ -657,6 +698,9 @@ namespace link { /// - `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; + /// There was an i/o error that lead to the failure of this operation. error IoError; } From ba85f09d93f7b4559f74ec3cf6aa2adfec08c8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 12 Feb 2026 15:10:56 +0100 Subject: [PATCH 34/42] Streamlines DHCPv4 support in the network namespace draft --- src/abi/src/network.abi | 435 ++++++++++++++++++++++++++++++++++++++++ src/abi/src/update.abi | 19 -- 2 files changed, 435 insertions(+), 19 deletions(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 38b2748f..378511c4 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -78,10 +78,17 @@ struct EndPoint { /// /// 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. +/// /// /// TODO: Write how routing works in Ashet OS. /// @@ -701,7 +708,435 @@ namespace link { /// 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; + } +} + +/// 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 index ea0fa75d..a4903f2c 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -133,36 +133,17 @@ namespace network.link { //? DHCP control - async_call RequestDhcp4Lease { - in interface: InterfaceId; - error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; - } - async_call RequestDhcp6Lease { in interface: InterfaceId; error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; } - struct Dhcp4Info { - field address: IP; - field prefix_len: u8; - field router: ?IP; - field dns_servers: []IP; - field valid_until: clock.Absolute; - } - struct Dhcp6Info { field addresses: []IP; field dns_servers: []IP; field valid_until: clock.Absolute; } - syscall get_dhcp4_info { - in interface: InterfaceId; - out info: Dhcp4Info; - error InvalidHandle, Gone, Unsupported; - } - syscall get_dhcp6_info { in interface: InterfaceId; out info: Dhcp6Info; From 2c9e99cde92dd63ec240efce714854bb9c729850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 13 Feb 2026 12:21:59 +0100 Subject: [PATCH 35/42] Extensively overhauls the link.InterfaceType and adds link.PhysicalAddress. --- src/abi/src/network.abi | 262 +++++++++++++++++++++++++---- src/abi/src/update.abi | 363 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 582 insertions(+), 43 deletions(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 378511c4..25e11e88 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -147,12 +147,205 @@ namespace link { /// The interface is a loopback interface. item loopback = 1; - /// The interface is an ethernet interface. + /// The interface is an Ethernet (IEEE 802.3) interface. item ethernet = 2; - /// The interface is virtual and is controlled by - /// a software. + /// 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 @@ -718,6 +911,15 @@ namespace link { } } +/// Neighbor discovery / neighbor cache (ARP for IPv4, NDP for IPv6). +namespace neighborhood { + //? TODO: Spec out this namespace +} + +namespace slaac { + //? TODO: Spec out this namespace +} + /// Everything related to DHCP management for IPv4. namespace dhcp4 { @@ -731,7 +933,7 @@ namespace dhcp4 { item router = 3; item domain_name_server = 6; - + item ip_address_lease_time = 51; item server_identifier = 54; @@ -747,7 +949,7 @@ namespace dhcp4 { /// A DHCP option. struct Option { - /// The type of option + /// The type of option field code: OptionCode; /// The value of the option. @@ -759,7 +961,7 @@ namespace dhcp4 { /// 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; + field value: []const u8; } /// A DHCP lease. @@ -769,10 +971,10 @@ namespace dhcp4 { /// - 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 { @@ -787,7 +989,7 @@ namespace dhcp4 { /// 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` @@ -797,14 +999,14 @@ namespace dhcp4 { 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; + 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. @@ -822,7 +1024,7 @@ namespace dhcp4 { /// 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 @@ -865,7 +1067,7 @@ namespace dhcp4 { /// configuration. item disabled = 0; - /// The kernel will only use the `Lease.address` and `Lease.prefix_len` to + /// The kernel will only use the `Lease.address` and `Lease.prefix_len` to /// perform an automatic `link.AddAddress` operation on `RequestLease.interface`. /// /// This includes: @@ -874,10 +1076,10 @@ namespace dhcp4 { 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 + /// 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 + /// 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. /// @@ -919,13 +1121,13 @@ namespace dhcp4 { 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`. + /// 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* @@ -936,7 +1138,7 @@ namespace dhcp4 { /// /// 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. /// @@ -944,12 +1146,12 @@ namespace dhcp4 { /// /// 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 + /// 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. /// @@ -964,7 +1166,7 @@ namespace dhcp4 { /// 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; @@ -975,7 +1177,7 @@ namespace dhcp4 { 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. @@ -987,10 +1189,10 @@ namespace dhcp4 { /// 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; @@ -1039,7 +1241,7 @@ namespace dhcp4 { /// 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 @@ -1065,7 +1267,7 @@ namespace dhcp4 { error InProgress; /// An invalid value was passed as a parameter. - /// + /// /// This may be due to: /// - `deadline` is not in the future. error InvalidValue; @@ -1095,8 +1297,8 @@ namespace dhcp4 { syscall get_info { /// Interface for which the lease shall be queried. in interface: link.InterfaceId; - - /// The current lease the kernel stores for `interface`. + + /// The current lease the kernel stores for `interface`. out info: Lease; /// `interface` is not a valid interface id (anymore). @@ -1136,7 +1338,7 @@ namespace dhcp6 { 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 index a4903f2c..142bee8d 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -126,29 +126,366 @@ Ashet OS networking model (current intent): - Destroying interfaces yields error.Gone to dependents. -namespace network.link { +namespace dhcp6 { - /// IP addressing + 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; + } - //? DHCP control + bitstruct RequestKind : u8 { + field addresses: bool; + field prefixes: bool; + reserve u6 = 0; + } - async_call RequestDhcp6Lease { - in interface: InterfaceId; - error InvalidHandle, Gone, Unsupported, SystemResources, Timeout, IoError; + struct IaAddress { + field address: IPv6; + field preferred_until: clock.Absolute; + field valid_until: clock.Absolute; } - struct Dhcp6Info { - field addresses: []IP; - field dns_servers: []IP; + struct IaPrefix { + field prefix: IPv6; + field prefix_len: u8; + field preferred_until: clock.Absolute; field valid_until: clock.Absolute; } - syscall get_dhcp6_info { - in interface: InterfaceId; - out info: Dhcp6Info; - error InvalidHandle, Gone, Unsupported; + 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; + } +} + + +/// IPv6 Stateless Address Autoconfiguration (SLAAC). +namespace slaac { + + enum Mode : u8 { + item disabled = 0; + item enabled = 1; + } + + enum Origin : u8 { + item slaac = 0; + } + + enum AddrKind : u8 { + item stable = 0; + item temporary = 1; + } + + bitstruct Flags : u8 { + field managed: bool; + field other_config: bool; + field on_link: bool; + field autonomous: bool; + reserve u4 = 0; + } + + struct Prefix { + field prefix: IPv6; + field prefix_len: u8; + field valid_until: clock.Absolute; + field preferred_until: clock.Absolute; + field flags: Flags; + field received_at: clock.Absolute; + } + + struct Router { + field address: IPv6; + field hop_limit: u8; + field mtu: u32; + field reachable_time_ms: u32; + field retrans_time_ms: u32; + field flags: Flags; + field received_at: clock.Absolute; + field valid_until: clock.Absolute; + } + + struct Config { + field enabled: Mode; + field add_stable: bool; + field add_temporary: bool; + field stable_kind: AddrKind; + field temp_kind: AddrKind; + field prefer_temporary: bool; + } + + struct Status { + field enabled: Mode; + field last_update: clock.Absolute; + field managed: bool; + field other_config: bool; + } + + enum UpdateKind : u8 { + item router = 0; + item prefix = 1; + item status = 2; + } + struct Update { + field kind: UpdateKind; + field interface: link.InterfaceId; + field router: Router; + field prefix: Prefix; + field status: Status; + } + + syscall get_config { + in interface: link.InterfaceId; + out config: Config; + error InvalidInterface; + } + + syscall set_config { + in interface: link.InterfaceId; + in config: Config; + error InvalidInterface, InvalidValue; + } + + syscall get_status { + in interface: link.InterfaceId; + out status: Status; + error InvalidInterface, SubsystemDisabled; + } + + syscall enumerate_routers { + in interface: link.InterfaceId; + in routers: ?[]Router; + out count: usize; + error InvalidInterface, SubsystemDisabled; + } + + syscall enumerate_prefixes { + in interface: link.InterfaceId; + in prefixes: ?[]Prefix; + out count: usize; + error InvalidInterface, SubsystemDisabled; + } + + async_call Refresh { + in interface: link.InterfaceId; + in deadline: clock.Absolute; + error InvalidInterface, Gone, Timeout, InvalidValue, SubsystemDisabled, LinkDown, IoError, SystemResources; + } + + syscall flush { + in interface: link.InterfaceId; + in keep_addresses: bool; + error InvalidInterface, SubsystemDisabled; + } + + async_call WaitForUpdate { + in interface: link.InterfaceId; + out update: Update; + error InvalidInterface, Gone, SystemResources; + } +} + +/// Neighbor discovery / neighbor cache (ARP for IPv4, NDP for IPv6). +namespace neighborhood { + + enum Protocol : u8 { + item arp = 0; + item ndp = 1; + item any = 2; + } + + enum Origin : u8 { + item manual = 0; + item learned = 1; + item autoconfig = 2; + } + + enum State : u8 { + item incomplete = 0; + item reachable = 1; + item stale = 2; + item delay = 3; + item probe = 4; + item failed = 5; + item permanent = 6; + } + + bitstruct EntryFlags : u8 { + field is_router: bool; + field is_proxy: bool; + reserve u6 = 0; + } + + struct Entry { + field protocol: Protocol; + field interface: link.InterfaceId; + field ip: IP; + field link_address: LinkAddress; + field state: State; + field origin: Origin; + field flags: EntryFlags; + field last_updated: clock.Absolute; + field valid_until: clock.Absolute; + } + + enum UpdateKind : u8 { + item added = 0; + item removed = 1; + item changed = 2; + item expired = 3; + item flushed = 4; + } + + struct Update { + field kind: UpdateKind; + field entry: Entry; + } + + syscall enumerate_entries { + in interface: link.InterfaceId; + in protocol: Protocol; + in entries: ?[]Entry; + out count: usize; + error InvalidInterface, InvalidValue; + } + + syscall get_entry { + in interface: link.InterfaceId; + in protocol: Protocol; + in ip: IP; + out entry: Entry; + error InvalidInterface, InvalidValue, NotAvailable; + } + + syscall add_entry { + in entry: Entry; + in replace: bool; + error InvalidInterface, InvalidValue, Conflict, SystemResources; + } + + syscall remove_entry { + in interface: link.InterfaceId; + in protocol: Protocol; + in ip: IP; + error InvalidInterface, InvalidValue; + } + + syscall flush_entries { + in interface: link.InterfaceId; + in protocol: Protocol; + in keep_manual: bool; + error InvalidInterface, InvalidValue; + } + + async_call Resolve { + in interface: link.InterfaceId; + in protocol: Protocol; + in target: IP; + in deadline: clock.Absolute; + out entry: Entry; + error InvalidInterface, Gone, Timeout, InvalidValue, SubsystemDisabled, LinkDown, IoError, SystemResources; + } + + async_call WaitForUpdate { + in interface: link.InterfaceId; + in protocol: Protocol; + out update: Update; + error InvalidInterface, Gone, SystemResources; + } } From cf1a83718660d9caad32517c1baedd38f057514c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Fri, 13 Feb 2026 12:29:15 +0100 Subject: [PATCH 36/42] Adds a getter and setter for physical addresses and includes it in the InterfaceDescription --- src/abi/src/network.abi | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 25e11e88..56b454c0 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -179,7 +179,6 @@ namespace link { /// /// 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; @@ -363,6 +362,9 @@ namespace link { /// 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 @@ -422,6 +424,34 @@ namespace link { 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 From 2e30d4710c2225a85dda44b5230cf1ad1d818382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 16 Feb 2026 19:16:17 +0100 Subject: [PATCH 37/42] Starts documenting the neighborhood api --- src/abi/src/network.abi | 296 ++++++++++++++++++++++++++++++++++++++++ src/abi/src/update.abi | 105 -------------- 2 files changed, 296 insertions(+), 105 deletions(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 56b454c0..78c072b5 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -23,6 +23,8 @@ struct IPv6 { /// 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. @@ -31,6 +33,11 @@ struct IP { 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 { @@ -944,6 +951,295 @@ namespace link { /// Neighbor discovery / neighbor cache (ARP for IPv4, NDP for IPv6). namespace neighborhood { //? TODO: Spec out this namespace + + /// Enumerates potential origins for a network neighbour. + enum Origin : u8 { + /// The neighbour 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: + /// TODO!!!! + /// - `known_since` + /// - `` + /// last_updated + /// reachable_until + /// expires_after + 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 + 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 strictly bigger + /// than `last_updated`. + /// + /// NOTE: When `clock.monotonic` returns a value bigger than this, the neighbor has + /// become stale. + /// This means the neighbor isn't strictly 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 reachable_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. + /// + /// TODO: Describe this flag better. + /// + /// NOTE: This value may change over time depending on the communications on the network. + /// + /// NOTE: For IPv4 neighbors, this value is always `false`. + field is_router: bool; + + /// This neighbor is proxied through network segments. + /// + /// TODO: Describe this flag better. + /// + /// NOTE: For IPv4 neighbors, this value is always `false`. + field is_proxy: bool; + + reserve u6 = 0; + } + + /// Enumeration of potential states a neighbor have. + enum State : u8 { + /// The neighbor is actively reachable and 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: 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: 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. + 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; + + error InvalidInterface, InvalidValue, 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. + in ip: IP; + + /// The neighbour entry for `ip`. + out neighbor: Neighbor; + + error InvalidInterface, InvalidValue, NotAvailable, Conflict; + } + + /// Adds a new static neighbor to the neighborhood. + 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; + + error InvalidInterface, InvalidValue, Conflict, 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 neighnor should be removed. + in interface: link.InterfaceId; + + /// The neighbor to be removed. + in ip: IP; + + /// Guardrail to prevent accidential removal of neighbors created + /// through neighbor discovery. + in include_learned: bool; + + error InvalidInterface, InvalidValue, Forbidden; + } + + syscall flush_neighbors { + in interface: link.InterfaceId; + in protocol: Protocol; + in keep_manual: bool; + error InvalidInterface, InvalidValue; + } + + /// 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 behaviour. + /// + /// 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 + 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 `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 deadline: clock.Absolute; + + /// Overwrites the kernels internal flood protection and immediatly 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; + + error InvalidInterface, Gone, Timeout, InvalidValue, NoRoute, SubsystemDisabled, LinkDown, IoError, SystemResources; + } + + /// 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; + } } namespace slaac { diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index 142bee8d..dff93a05 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -381,111 +381,6 @@ namespace slaac { } } -/// Neighbor discovery / neighbor cache (ARP for IPv4, NDP for IPv6). namespace neighborhood { - enum Protocol : u8 { - item arp = 0; - item ndp = 1; - item any = 2; - } - - enum Origin : u8 { - item manual = 0; - item learned = 1; - item autoconfig = 2; - } - - enum State : u8 { - item incomplete = 0; - item reachable = 1; - item stale = 2; - item delay = 3; - item probe = 4; - item failed = 5; - item permanent = 6; - } - - bitstruct EntryFlags : u8 { - field is_router: bool; - field is_proxy: bool; - reserve u6 = 0; - } - - struct Entry { - field protocol: Protocol; - field interface: link.InterfaceId; - field ip: IP; - field link_address: LinkAddress; - field state: State; - field origin: Origin; - field flags: EntryFlags; - field last_updated: clock.Absolute; - field valid_until: clock.Absolute; - } - - enum UpdateKind : u8 { - item added = 0; - item removed = 1; - item changed = 2; - item expired = 3; - item flushed = 4; - } - - struct Update { - field kind: UpdateKind; - field entry: Entry; - } - - syscall enumerate_entries { - in interface: link.InterfaceId; - in protocol: Protocol; - in entries: ?[]Entry; - out count: usize; - error InvalidInterface, InvalidValue; - } - - syscall get_entry { - in interface: link.InterfaceId; - in protocol: Protocol; - in ip: IP; - out entry: Entry; - error InvalidInterface, InvalidValue, NotAvailable; - } - - syscall add_entry { - in entry: Entry; - in replace: bool; - error InvalidInterface, InvalidValue, Conflict, SystemResources; - } - - syscall remove_entry { - in interface: link.InterfaceId; - in protocol: Protocol; - in ip: IP; - error InvalidInterface, InvalidValue; - } - - syscall flush_entries { - in interface: link.InterfaceId; - in protocol: Protocol; - in keep_manual: bool; - error InvalidInterface, InvalidValue; - } - - async_call Resolve { - in interface: link.InterfaceId; - in protocol: Protocol; - in target: IP; - in deadline: clock.Absolute; - out entry: Entry; - error InvalidInterface, Gone, Timeout, InvalidValue, SubsystemDisabled, LinkDown, IoError, SystemResources; - } - - async_call WaitForUpdate { - in interface: link.InterfaceId; - in protocol: Protocol; - out update: Update; - error InvalidInterface, Gone, SystemResources; - } } From 820fbdb8fa7c9e747c83c20f2cdafc8143931818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 16 Feb 2026 21:51:34 +0100 Subject: [PATCH 38/42] Overhauls network.neighborhood into a final state (excluding WaitForUpdate) --- src/abi/src/network.abi | 211 ++++++++++++++++++++++++++++++---------- src/abi/src/update.abi | 20 ++++ 2 files changed, 181 insertions(+), 50 deletions(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 78c072b5..ff1f38ed 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -950,11 +950,9 @@ namespace link { /// Neighbor discovery / neighbor cache (ARP for IPv4, NDP for IPv6). namespace neighborhood { - //? TODO: Spec out this namespace - - /// Enumerates potential origins for a network neighbour. + /// Enumerates potential origins for a network neighbor. enum Origin : u8 { - /// The neighbour was manually created with `add_neighbor`. + /// The neighbor was manually created with `add_neighbor`. item manual = 0; /// The neighbor was discovered by the kernel through an ARP/NDP request. @@ -968,12 +966,11 @@ namespace neighborhood { /// A neighbor is the mapping of an IP address to a physical address. /// /// The following invariants apply to the timestamp values: - /// TODO!!!! - /// - `known_since` - /// - `` - /// last_updated - /// reachable_until - /// expires_after + /// - `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; @@ -983,6 +980,8 @@ namespace neighborhood { /// 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. @@ -1025,17 +1024,17 @@ namespace neighborhood { /// /// NOTE: For IPv6/NDP, this is derived from the ReachableTime. /// - /// NOTE: As long as `state == State.reachable`, this value is strictly bigger + /// 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 strictly valid anymore, but the kernel assumes + /// 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 reachable_until: clock.Absolute; + field fresh_until: clock.Absolute; /// The drop-dead time after which this neighbor is killed. /// @@ -1047,16 +1046,14 @@ namespace neighborhood { bitstruct Flags : u8 { /// This neighbor is believed to be an IPv6 capable router on the link. /// - /// TODO: Describe this flag better. - /// - /// NOTE: This value may change over time depending on the communications on the network. + /// 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. /// - /// TODO: Describe this flag better. + /// 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; @@ -1064,20 +1061,38 @@ namespace neighborhood { reserve u6 = 0; } - /// Enumeration of potential states a neighbor have. + /// 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; } @@ -1091,7 +1106,8 @@ namespace neighborhood { /// Defines for which IP protocols the neighbors should be returned. /// - /// NOTE: If `IP.Type.any` is passed, both IPv4 and IPv6 neighbors are 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 @@ -1109,7 +1125,14 @@ namespace neighborhood { /// the total number of neighbors returned by the query. out count: usize; - error InvalidInterface, InvalidValue, SubsystemDisabled; + /// `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. @@ -1122,24 +1145,76 @@ namespace neighborhood { 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 neighbour entry for `ip`. + /// The neighbor entry for `ip`. out neighbor: Neighbor; - error InvalidInterface, InvalidValue, NotAvailable, Conflict; + /// `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 + /// 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; - error InvalidInterface, InvalidValue, Conflict, SystemResources; + /// `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. @@ -1150,24 +1225,57 @@ namespace neighborhood { /// /// NOTE: If no neighbor with `ip` exists, this syscall is idempotent. syscall remove_neighbor { - /// The interface for which the neighnor should be removed. + /// 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 accidential removal of neighbors created + /// Guardrail to prevent accidental removal of neighbors created /// through neighbor discovery. in include_learned: bool; - error InvalidInterface, InvalidValue, Forbidden; + /// `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; - in protocol: Protocol; + + /// 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; - error InvalidInterface, InvalidValue; + + /// `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. @@ -1177,7 +1285,7 @@ namespace neighborhood { /// /// This is to prevent flooding the network with requests. /// - /// `flood` overwrites this behaviour. + /// `flood` overwrites this behavior. /// /// NOTE: Not every schedule of a resolve triggers a new discovery. /// The kernel is free to fuse several scheduled `Resolve` @@ -1195,19 +1303,21 @@ namespace neighborhood { /// 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 `Ping` would potentially + /// 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 immediatly starts + /// Overwrites the kernels internal flood protection and immediately starts /// sending a request. /// /// NOTE: Setting `flood` disables the internal fusing and the kernel will trigger @@ -1217,28 +1327,29 @@ namespace neighborhood { /// The information received by the resolver. out neighbor: Neighbor; - error InvalidInterface, Gone, Timeout, InvalidValue, NoRoute, SubsystemDisabled, LinkDown, IoError, SystemResources; - } + /// `interface` is not a valid interface id (anymore). + error InvalidInterface; - /// 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; - } + /// The underlying `interface` was removed during this operation. + error Gone; - /// Waits until the kernel updates/edits the list of neighbors. - async_call WaitForUpdate { - in interface: link.InterfaceId; + /// No response was received until `deadline`. + error Timeout; - /// NOTE: Cannot be `IP.Type.any`. - in protocol: IP.Type; - out kind: UpdateKind; - out entry: Entry; + /// `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; - error InvalidInterface, Gone, SystemResources, SubsystemDisabled; + /// There was an i/o error that lead to the failure of this operation. + error IoError; + + error SystemResources; } } diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index dff93a05..67705b45 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -383,4 +383,24 @@ namespace slaac { 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; + } } From c58f1fd4575c3471b02e35cfad5598670c5ded8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 16 Feb 2026 22:35:26 +0100 Subject: [PATCH 39/42] Freshens up the SLAAC draft --- src/abi/src/network.abi | 11 ++++++++- src/abi/src/update.abi | 55 +++++++++++++++-------------------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index ff1f38ed..2175618c 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -96,6 +96,11 @@ struct EndPoint { /// /// 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. /// @@ -361,8 +366,9 @@ namespace link { field ipv6: bool; field dhcp4: bool; field dhcp6: bool; + field slaac: bool; - reserve u28 = 0; + reserve u27 = 0; } struct InterfaceDescription { @@ -1351,8 +1357,11 @@ namespace neighborhood { 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: Spec out this namespace } diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index 67705b45..4b556ce5 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -257,16 +257,6 @@ namespace dhcp6 { /// IPv6 Stateless Address Autoconfiguration (SLAAC). namespace slaac { - - enum Mode : u8 { - item disabled = 0; - item enabled = 1; - } - - enum Origin : u8 { - item slaac = 0; - } - enum AddrKind : u8 { item stable = 0; item temporary = 1; @@ -300,36 +290,35 @@ namespace slaac { field valid_until: clock.Absolute; } + enum StableAddrMethod : u8 { + /// Don't generate an address at all. + item none = 0; + + item eui64 = 1; + + /// RFC 7217 + item stable_privacy = 2; + } + + enum TempAddrMethod : u8{ + /// Don't generate an address at all. + item none = 0; + + item rfc4941 = 1; + } + struct Config { - field enabled: Mode; - field add_stable: bool; - field add_temporary: bool; - field stable_kind: AddrKind; - field temp_kind: AddrKind; + field stable_method: StableAddrMethod; + field temp_method: TempAddrMethod; field prefer_temporary: bool; } struct Status { - field enabled: Mode; field last_update: clock.Absolute; field managed: bool; field other_config: bool; } - enum UpdateKind : u8 { - item router = 0; - item prefix = 1; - item status = 2; - } - - struct Update { - field kind: UpdateKind; - field interface: link.InterfaceId; - field router: Router; - field prefix: Prefix; - field status: Status; - } - syscall get_config { in interface: link.InterfaceId; out config: Config; @@ -374,11 +363,7 @@ namespace slaac { error InvalidInterface, SubsystemDisabled; } - async_call WaitForUpdate { - in interface: link.InterfaceId; - out update: Update; - error InvalidInterface, Gone, SystemResources; - } + //? TODO: Add "async_call WaitForUpdate" when a proper model was found,. } namespace neighborhood { From 692e67e778b37cdbde93a97bc34528b6b953f8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Mon, 16 Feb 2026 22:40:40 +0100 Subject: [PATCH 40/42] Fixes flags for slaac --- src/abi/src/update.abi | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index 4b556ce5..94598c12 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -257,19 +257,6 @@ namespace dhcp6 { /// IPv6 Stateless Address Autoconfiguration (SLAAC). namespace slaac { - enum AddrKind : u8 { - item stable = 0; - item temporary = 1; - } - - bitstruct Flags : u8 { - field managed: bool; - field other_config: bool; - field on_link: bool; - field autonomous: bool; - reserve u4 = 0; - } - struct Prefix { field prefix: IPv6; field prefix_len: u8; @@ -277,6 +264,12 @@ namespace slaac { field preferred_until: clock.Absolute; field flags: Flags; field received_at: clock.Absolute; + + bitstruct Flags : u8 { + field on_link: bool; + field autonomous: bool; + reserve u6 = 0; + } } struct Router { @@ -288,6 +281,13 @@ namespace slaac { field flags: Flags; field received_at: clock.Absolute; field valid_until: clock.Absolute; + + + bitstruct Flags : u8 { + field managed: bool; + field other_config: bool; + reserve u6 = 0; + } } enum StableAddrMethod : u8 { From 1dc695b6ce98054fa346f88ad643610ba9f397d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Thu, 19 Feb 2026 12:42:01 +0100 Subject: [PATCH 41/42] Defines namespace slaac. --- src/abi/src/network.abi | 484 +++++++++++++++++++++++++++++++++++++++- src/abi/src/update.abi | 117 +--------- 2 files changed, 489 insertions(+), 112 deletions(-) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index 2175618c..e87623f1 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -1363,7 +1363,489 @@ namespace neighborhood { /// IPv6 Stateless Address Autoconfiguration (SLAAC). namespace slaac { - //? TODO: Spec out this namespace + + //? 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. diff --git a/src/abi/src/update.abi b/src/abi/src/update.abi index 94598c12..a8c44e64 100644 --- a/src/abi/src/update.abi +++ b/src/abi/src/update.abi @@ -126,6 +126,12 @@ Ashet OS networking model (current intent): - Destroying interfaces yields error.Gone to dependents. +namespace link { + + //? TODO: field prefer_temporary: bool; + +} + namespace dhcp6 { enum OptionCode : u16 { @@ -255,117 +261,6 @@ namespace dhcp6 { } -/// IPv6 Stateless Address Autoconfiguration (SLAAC). -namespace slaac { - struct Prefix { - field prefix: IPv6; - field prefix_len: u8; - field valid_until: clock.Absolute; - field preferred_until: clock.Absolute; - field flags: Flags; - field received_at: clock.Absolute; - - bitstruct Flags : u8 { - field on_link: bool; - field autonomous: bool; - reserve u6 = 0; - } - } - - struct Router { - field address: IPv6; - field hop_limit: u8; - field mtu: u32; - field reachable_time_ms: u32; - field retrans_time_ms: u32; - field flags: Flags; - field received_at: clock.Absolute; - field valid_until: clock.Absolute; - - - bitstruct Flags : u8 { - field managed: bool; - field other_config: bool; - reserve u6 = 0; - } - } - - enum StableAddrMethod : u8 { - /// Don't generate an address at all. - item none = 0; - - item eui64 = 1; - - /// RFC 7217 - item stable_privacy = 2; - } - - enum TempAddrMethod : u8{ - /// Don't generate an address at all. - item none = 0; - - item rfc4941 = 1; - } - - struct Config { - field stable_method: StableAddrMethod; - field temp_method: TempAddrMethod; - field prefer_temporary: bool; - } - - struct Status { - field last_update: clock.Absolute; - field managed: bool; - field other_config: bool; - } - - syscall get_config { - in interface: link.InterfaceId; - out config: Config; - error InvalidInterface; - } - - syscall set_config { - in interface: link.InterfaceId; - in config: Config; - error InvalidInterface, InvalidValue; - } - - syscall get_status { - in interface: link.InterfaceId; - out status: Status; - error InvalidInterface, SubsystemDisabled; - } - - syscall enumerate_routers { - in interface: link.InterfaceId; - in routers: ?[]Router; - out count: usize; - error InvalidInterface, SubsystemDisabled; - } - - syscall enumerate_prefixes { - in interface: link.InterfaceId; - in prefixes: ?[]Prefix; - out count: usize; - error InvalidInterface, SubsystemDisabled; - } - - async_call Refresh { - in interface: link.InterfaceId; - in deadline: clock.Absolute; - error InvalidInterface, Gone, Timeout, InvalidValue, SubsystemDisabled, LinkDown, IoError, SystemResources; - } - - syscall flush { - in interface: link.InterfaceId; - in keep_addresses: bool; - error InvalidInterface, SubsystemDisabled; - } - - //? TODO: Add "async_call WaitForUpdate" when a proper model was found,. -} - namespace neighborhood { /// Enumerates the ways the kernel can update/edit the list of neighbours. From fe3d78bc086899e5ba063807e40d6c4ac738b25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20=22xq=22=20Quei=C3=9Fner?= Date: Sat, 21 Feb 2026 12:29:15 +0100 Subject: [PATCH 42/42] Adds TODO for the DAD/ARP when doing async AddAddress to prevent accidential misconfiguration --- src/abi/src/network.abi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/abi/src/network.abi b/src/abi/src/network.abi index e87623f1..f246dd72 100644 --- a/src/abi/src/network.abi +++ b/src/abi/src/network.abi @@ -586,7 +586,14 @@ namespace link { /// 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;