Introduction

Ariel OS is an operating system for secure, memory-safe, networked applications running on low-power microcontrollers. It is based on Rust from the ground up and supports hardware based on 32-bit microcontroller architectures (Cortex-M, RISC-V, and Xtensa). Many features provided by Ariel OS can individually be enabled or disabled at build time in order to minimize resource consumption.

Goals and Design

Ariel OS builds on top of existing projects from the Embedded Rust ecosystem, including Embassy, esp-hal, defmt, probe-rs, sequential-storage, and embedded-test.

Ariel OS follows an approach whereby it simultaneously integrates a curated ecosystem of libraries (available via crates.io), and adds missing operating system functionalities as depicted below. Such functionalities include a preemptive multicore scheduler, portable peripheral APIs, additional network security facilities, as well as a meta-build system to bind it all together.

As a result, a low-power IoT developer can focus on business logic sitting on top of APIs which remain close to the hardware but nevertheless stay the same across all supported hardware, inspired by what RIOT tends to in that regard. The intent is three-fold: reduce application development time, increase code portability, and minimize core system vulnerabilities.

In a nutshell: Ariel OS contributes to the global effort aiming to (re)write IoT system software foundations on more solid ground than what traditional building blocks written in C can provide. And this is a joyful and welcoming open-source community, so: join us!

Architecture diagram
Architecture diagram of Ariel OS

Further Reading

Even though not necessary for using Ariel OS, readers of this book might also want to have a look at the following resources:

  • The Embedded Rust Book: General information about how to use Rust for embedded and the structure of the Rust embedded ecosystem, which Ariel OS abstracts over.
  • Embassy Book: Hardware abstractions and async executors that Ariel OS builds on.

Hardware & Functionality Support

The table below indicates whether we support using the piece of functionality in a portable manner, trough an abstraction layer and platform-aware configuration.

Chip Testing Board Functionality
Manufacturer Name Ariel OS Name Manufacturer Name Ariel OS Name GPIO Debug Output I2C Controller Mode SPI Main Mode Logging User USB Wi-Fi Ethernet over USB Hardware Random Number Generator Persistent Storage
nRF52833 nrf52833 BBC micro:bit V2 bbc-microbit-v2
ESP32-C6 esp32c6 Espressif ESP32-C6-DevKitC-1 espressif-esp32-c6-devkitc-1 ☑️
ESP32-S3 esp32s3 Espressif ESP32-S3-DevKitC-1 espressif-esp32-s3-devkitc-1 ☑️
nRF52840 nrf52840 nRF52840-DK nrf52840dk
nRF5340 nrf5340 nRF5340-DK nrf5340dk
RP2040 rp2040 Raspberry Pi Pico rpi-pico
RP2040 rp2040 Raspberry Pi Pico W rpi-pico-w
STM32F401RETX stm32f401retx ST NUCLEO-F401RE st-nucleo-f401re
STM32F755ZITX stm32h755zitx ST NUCLEO-H755ZI-Q st-nucleo-h755zi-q
STM32W55RGVX stm32wb55rgvx ST NUCLEO-WB55RG st-nucleo-wb55

Key:

supported
☑️
supported with some caveats
available in hardware, but not currently supported by Ariel OS
not available on this piece of hardware

Getting started with Ariel OS

This guide is intended to get you started in about 5 minutes. It explains how to compile and run the hello-word example to verify your setup, and how to bootstrap a new application.

Currently only GNU/Linux is supported in this guide.

Installing the build prerequisites

  1. Install the needed build dependencies. On Ubuntu, the following is sufficient:
apt install build-essential curl git python3 pkg-config \
  libssl-dev llvm-dev cmake libclang-dev gcc-arm-none-eabi \
  clang unzip lld ninja-build
  1. Install the Rust installer rustup using the website's instructions or through your distribution package manager.

  2. Install the build system laze:

cargo install laze
  1. Install the debugging and flashing utility probe-rs:
cargo install --locked probe-rs-tools
  1. Clone the Ariel OS repository and cd into it.

  2. Install the Rust targets:

laze build install-toolchain

Running the hello-world example

To check that everything is installed correctly, the hello-word example can be compiled and run from the ariel-os directory. The following assumes you have your target board connected to your host computer.

Find the Ariel OS name of your supported board in the support matrix.

The following assumes the Nordic nRF52840-DK, whose Ariel OS name is nrf52840dk. Replace that name with your board's.

Then, from the ariel-os directory, compile and run the example, as follows:

laze -C examples/hello-world build -b nrf52840dk run
(This might fail if the flash is locked, click here for unlocking instructions.) This might fail due to a locked chip, e.g., on most nRF52840-DK boards that are fresh from the factory. In that case, the above command throws an error that ends with something like this:
An operation could not be performed because it lacked the permission to do so: erase_all

The chip can be unlocked using this command:

laze -C examples/hello-world build -b nrf52840dk flash-erase-all

Terminal screencast of compiling and flashing the hello-world example

If you do not plan on working on Ariel OS itself, this repository is not needed anymore and can be deleted.

Starting an application project from a template repository

Applications are expected to be developed out-of-tree, outside of the ariel-os directory. This is made possible by laze's imports feature.

To start a new application project, you can either clone the ariel-os-hello repository or, alternatively, use one of the cargo-generate templates.

Cloning ariel-os-hello

git clone https://github.com/ariel-os/ariel-os-hello

Using a cargo-generate project template

This requires installing cargo-generate, then a new application project can be created as follows:

cargo generate --git https://github.com/ariel-os/ariel-os-template --name <new-project-name>

Running the template example

To check your setup, the default application can be compiled and run as follows:

laze build -b nrf52840dk run

The board name needs to be replaced with your board's.

See the Build System page to learn more about laze and how to work with out-of-tree applications.

laze

Ariel OS makes use of the laze build system to run cargo with the correct parameters for a specific board and application.

laze provides a laze build -b <board> command, which in Ariel OS, internally uses cargo build.

laze commands are by default applied to the application(s) within the directory laze is run. For example, when run in examples/hello-world, laze build -b nrf52840dk would build the hello-world example for the nrf52840dk board. Alternatively, the -C option can be used to switch to the given directory.

laze allows to override global variables using e.g., -DFOO=BAR.

laze tasks

For tasks like flashing and debugging, Ariel OS uses laze tasks. laze tasks currently have the syntax laze build -b <board> [other options] <task-name>. For example, to run the hello-world example from the ariel-os directory, the command would be:

laze -C examples/hello-world build -b nrf52840dk run

Tasks available in Ariel OS include:

  • run: Compiles, flashes, and runs an application. The debug output is printed in the terminal.
  • flash: Compiles and flashes an application.
  • debug: Starts a GDB debug session for the selected application. The application needs to be flashed using the flash task beforehand.
  • flash-erase-all: Erases the entire flash memory, including user data. Unlocks it if locked.
  • reset: Reboots the target.
  • tree: Prints the application's cargo tree.

As some tasks may trigger a rebuild, it is necessary to pass the same settings to related consecutive commands: laze build -DFOO=1 flash followed by laze build -DFOO=other debug might not work as expected, as the second command could be rebuilding the application before starting the debug session.

laze modules

laze allows enabling/disabling individual features using modules, which can be selected or disabled on the command line using --select <module> or --disable <module>. To specify laze modules for an out-of-tree application, see below.

Modules available in Ariel OS include:

  • silent-panic: Disables printing panics. May help reduce binary size.

Other modules are documented in their respective pages.

laze contexts

The laze configuration defines a laze context for each MCU, MCU family, and board. These can be found in the support matrix, where they are called “Ariel OS name”.

Out-of-tree applications can be restricted to specific laze contexts, see below.

In addition, laze passes the names of all contexts related to the selected builder as rustc --cfg context=$CONTEXT flags. This makes it possible to use the #[cfg] attribute to introduce feature-gates based on the MCU, MCU family, or board, when required.

Out-of-tree applications

New application projects should be started from a template. Out-of-tree applications use the laze-project.yml file for configuration through laze.

Importing Ariel OS

Ariel OS's source and configuration are imported using laze's imports feature. The project templates use a git import to ask laze to clone Ariel OS's repository. The cloned repository is stored inside build/imports.

It is currently recommended to use Ariel OS's commit ID to track the repository, to avoid surprising changes. This commit ID needs to be updated to update the version of Ariel OS used by the application.

It is alternatively possible to clone the repository manually and specify the resulting directory using a path import. This can be useful when needing to modify Ariel OS itself, when also working on an application.

Enabling laze modules for an application

Instead of manually specifying laze modules on the command line, laze modules required for an application must be specified in the application's laze configuration file, laze-project.yml.

The selects array allows to specify a list of laze modules that will be enabled for the application, as follows:

apps:
  - name: <project-name>
    selects:
      - network
      - random

Note that, while the CLI option is named --select, the configuration key is selects.

The specified modules will be enabled for the application, some of which may enable associated Cargo features (as individually documented for each laze module). If a module is not available on a target—e.g., because networking is not available on the target, or not yet supported by Ariel OS—laze will prevent the application to be compiled for that target.

Forbidding laze modules for an application

Conversely, to forbid laze modules through the configuration file, the conflicts array is used:

apps:
  - name: <project-name>
    selects:
      - sw/threading
    conflicts:
      - multi-core

This enables support for multithreading, but disables multicore usage.

Restricting an application to specific MCUs/boards

Finally, an application may be restricted to specific MCUs, MCU families, or boards by explicitly specifying laze contexts the application is allowed to be compiled for:

apps:
  - name: <project-name>
    context:
      - bbc-microbit-v2
      - nrf52
      - nrf5340
      - rpi-pico-w

Building an Application

This chapters covers fundamental concepts required to build an Ariel OS application.

Obtaining Peripheral Access

Embassy defines a type for each MCU peripheral, which needs to be provided to the driver of that peripheral. These peripheral types, which we call Embassy peripherals or peripheral ZSTs, are Zero Sized Types (ZSTs) that are used to statically enforce exclusive access to a peripheral. These ZSTs indeed are by design neither Copy nor Clone, making it impossible to duplicate them; they can only be moved around.

Drivers therefore require such ZSTs to be provided to make sure that the caller has (a) access to the peripheral and (b) is the only one having access, since only a single instance of the type can exist at any time. Being ZSTs, they do not carry any data to the drivers, only their ownership is meaningful, which is enforced by taking them as parameters for drivers.

If you are used to thinking about MCU peripherals as referenced by a base address (in the case of memory-mapped peripherals), you can think of these ZSTs as abstraction over these, with a zero-cost, statically-enforced lock ensuring exclusive access.

These Embassy types are defined by Embassy HAL crates in the respective peripherals modules. In Ariel OS applications, the only safe way to obtain an instance of an Embassy peripheral is by using the define_peripherals! macro, combined with a spawner or task. The group_peripherals! macro can also be useful.

Example

The define_peripherals! macro allows to define a Ariel OS peripheral struct, an instance of which can be obtained with spawner or task:

ariel_os::hal::define_peripherals!(LedPeripherals { led: P0_13 });

Multiple Ariel OS peripheral structs can be grouped into another Ariel OS peripheral struct using the group_peripherals! macro:

ariel_os::hal::group_peripherals!(Peripherals {
    leds: LedPeripherals,
    buttons: ButtonPeripherals,
});

Similarly to LedPeripherals, an instance of the Peripherals Ariel OS peripheral struct thus defined can be obtained with spawner or task.

The spawner and task Ariel OS macros

Unlike traditional Rust programs, Ariel OS applications do not have a single entrypoint. Instead, multiple functions can be registered to be started during boot. Functions can currently be registered as either spawners or tasks:

  • spawner functions are non-async and should be used when no async functions need to be called. They are provided with a Spawner instance and can therefore be used to spawn other async tasks.
  • task functions are async functions that are statically allocated at compile-time. They are especially useful for long-running, async tasks. They must also be used to use Ariel OS configuration hooks, which can be requested with their associated macro parameter, and allow to provide configuration during boot. Please refer to the documentation of task for a list of available hooks and to Configuration Hooks to know more about hook usage.

Both of these can be provided with an instance of an Ariel OS peripheral struct when needed, using the peripherals macro parameters (see the macros' documentation) and taking that Ariel OS peripheral struct as parameter.

The Embassy peripherals obtained this way are regular Embassy peripherals, which are compatible with both Ariel OS portable drivers and Embassy HAL crates' HAL-specific drivers.

Examples

Here is an example of the task macro (the pins module internally uses define_peripherals!) from the blinky example:

#[ariel_os::task(autostart, peripherals)]
async fn blinky(peripherals: pins::LedPeripherals) {
    let mut led = Output::new(peripherals.led, Level::Low);

    loop {
        led.toggle();
        Timer::after(Duration::from_millis(500)).await;
    }
}

Configuration Hooks

TODO

Debug Logging

Ariel OS supports debug logging on all platforms. It is enabled by default.

Logging

Within Rust code, import ariel_os::debug::log items, then use Ariel OS logging macros:

use ariel_os::debug::log::info;

#[ariel_os::task(autostart)]
async fn main() {
    info!("Hello!");
}

Filtering Logs

In Ariel OS, the log level defaults to info. It can be configured using the laze variable LOG. Depending on the logger, it may be possible to configure different levels per crate or per module.

Example:

$ laze build -C examples/log --builders nrf52840dk -DLOG=info run

Logging Facades and Loggers

Ariel OS supports multiple logging facades and loggers. Only one of them may be enabled at a time; if none of them are enabled, logging statements become no-operations. Enabling either the defmt or log laze modules allows selecting which logging facade and logger is used. defmt should be preferred when possible as it results in smaller binaries.

defmt

See the defmt documentation for general info on the defmt's facade and logger.

The defmt logger supports configuring the log level per crate and per module, as follows:

$ laze build -C examples/log --builders nrf52840dk -DLOG=info,ariel_os_rt=trace run

Note: On Cortex-M devices, the order of ariel_os::debug::println!() output and defmt log output is not deterministic.

log

Ariel OS's logger for log supports configuring the log level globally, but does not currently support per-crate filtering.

Networking

Ariel OS currently supports two different networking links: Ethernet-over-USB (aka CDC-NCM) and Wi-Fi. Boards may support both of them, only one of them, or none of them. However, currently the network stack supports at most one interface.

Which link layer is used for networking must be selected at compile time, through laze modules:

  • usb-ethernet: Selects Ethernet-over-USB.
  • wifi-cyw43: Selects Wi-Fi using the CYW43 chip along an RP2040 MCU (e.g., on the Raspberry Pi Pico W).
  • wifi-esp: Selects Wi-Fi on an ESP32 MCU.

When available on the device, one of these module is always selected by default, currently preferring Wi-Fi networking. Overriding this default selection is possible by explicitly selecting the desired module.

Network Credentials

For Wi-Fi, the network credentials have to be supplied via environment variables:

CONFIG_WIFI_NETWORK=<ssid> CONFIG_WIFI_PASSWORD=<pwd> laze build ...

Network Configuration

DHCPv4 is used by default for network configuration, including for IP address allocation. This is enabled by the network-config-dhcp laze module, selected by default.

In order to provide a static configuration, select the network-config-static laze module, which will take precedence. The configuration can be customized with the following environment variables:

VariableDefault
CONFIG_NET_IPV4_STATIC_ADDRESS10.42.0.61
CONFIG_NET_IPV4_STATIC_CIDR_PREFIX_LEN24
CONFIG_NET_IPV4_STATIC_GATEWAY_ADDRESS10.42.0.1

Non-static IPv6 address allocation will be supported in the future.

Support for Network Protocols

Support for various network protocols can be enabled through Cargo features listed in the documentation. Most of these use embassy_net, which should be used through the ariel_os::reexports::embassy_net re-export.

Using the Network Stack

A network stack handle can then be obtained using ariel_os::net::network_stack().

See the examples for details.

Host Setup

Static IPv4 Address Configuration

When using a device with a static IPv4 address, the host computer can be configured as follows (where host_address is an IP address configured as gateway for the device):

# ip address add <host_address>/24 dev <interface>
# ip link set up dev <interface>

To verify that the address has indeed be added, you can use:

ip address show dev <interface>

Replace <interface> with the name of the used network interface. To find out the name of your interface you can use a command such as ip address.

Ethernet-over-USB

For Ethernet-over-USB, ensure that, in addition to the USB cable used for flashing and debugging, the user USB port is also connected to the host computer with a second cable.

Multithreading

Ariel OS supports multithreading on the Cortex-M, RISC-V, and Xtensa architectures, and is compatible with async executors.

Important: When an application requires multithreading, it must enable it by selecting the sw/threading laze module, which enables the threading Cargo feature.

Spawning Threads

The recommended way of starting threads is by using the #[ariel_os::thread] attribute macro, which creates and starts the thread during startup. Threads can also be spawned dynamically at runtime. In this case, the thread stack must still be statically allocated at compile time.

The maximum number of threads is defined by the THREAD_COUNT constant.

Scheduling

Multicore Support

Ariel OS currently supports symmetric multiprocessing (SMP) on the following MCUs:

  • ESP32-S3
  • RP2040

When the sw/threading laze module is selected and when available on the MCU, the multi-core laze module automatically gets selected, which enables SMP. To disable multicore, disable the multi-core laze module.

Porting single-core applications to support multicore requires no changes to them.

Priority Scheduling

Ariel OS features a preemptive scheduler, which supports priority scheduling with up to SCHED_PRIO_LEVELS priority levels. The highest priority runnable thread (or threads in the multicore case) is always executed. Threads having the same priority are scheduled cooperatively. The scheduler itself is tickless, therefore time-slicing isn't supported. Thread priorities are dynamic and can be changed at runtime using thread::set_priority().

On multicore, a single global runqueue is shared across all cores. The scheduler assigns the C highest-priority, ready, and non-conflicting threads to the C available cores. The scheduler gets invoked individually on each core. Whenever a higher priority thread becomes ready, the scheduler is triggered on the core with the lowest-priority running thread to perform a context switch.

Idling

On single core, no idle threads are created. Instead, if no threads are to be scheduled, the processor enters sleep mode until a thread is ready.

On multicore, one idle thread is created for each core. When an idle thread is scheduled, it prompts the current core to enter sleep mode.

Core Affinity

Core affinity, also known as core pinning, is optionally configurable for each thread. It allows to restrict the execution of a thread to a specific core and prevent it from being scheduled on another one.

Testing

Ariel OS supports in-hardware testing using the embedded-test crate. embedded-test, used in conjunction with probe-rs, serves as a replacement for the regular cargo test based test harness, as the latter cannot be used on no_std (embedded) devices. Please refer to the embedded-test documentation for more info.

The build system of Ariel OS integrates the embedded-test-based testing so that once set up, tests can be run by issuing laze build -b <board> test. embedded-tests can be used for any target that has probe-rs support (which currently means all targets). Both async and non-async code can be tested.

Currently, Ariel OS requires a fork of embedded-test. When using Ariel's build system, this will be used automatically.

Differences from vanilla embedded-test

In Ariel OS, the OS itself will start and initialize components before the tests are run. Logging, networking, ... will be available as for regular Ariel OS applications.

As a consequence, no Cargo features other than ariel-os should be enabled on the embedded-test dependency. In order to not require default-features = false, the (default) panic-handler feature is ignored when the ariel-os feature is enabled.

Setting up embedded-test for Ariel OS applications or libraries

Steps for enabling tests:

  1. Add embedded-test as a dev-dependency of your crate, and enable its ariel-os Cargo feature, as follows:
[dev-dependencies]
embedded-test = { version = "0.5.0", features = ["ariel-os"] }
  1. Disable the default test harness:

This depends on whether a lib, a bin or a separate test should be tested.

Add the following to your Cargo.toml:

# for a library crate
[lib]
harness = false

or

# for the default `bin`, "name" needs to match the package name
[[bin]]
name = "ariel-os-hello"
harness = false

or

# for a separate test in `test.rs`
[[test]]
name = "test"
harness = false
  1. Enable the embedded-test or embedded-test-only laze module:
apps:
# for an application:
  - name: your-application
    selects:
      - embedded-test

# for a library:
  - name: crate/your-library
    selects:
      - embedded-test-only

Even a library crate needs an entry in laze's apps in order to make the test task available. Selecting embedded-test-only will make sure that laze run is disabled.

  1. Add the following boilerplate to lib.rs, main.rs or test.rs:
#![allow(unused)]
fn main() {
This goes to the top of the file
#![no_main]
#![no_std]
#![feature(used_with_arg)]
#![feature(impl_trait_in_assoc_type)]
}
  1. Write the tests:
#![allow(unused)]
fn main() {
#[cfg(test)]
#[embedded_test::tests]
mod tests {
    // Optional: An init function which is called before every test
    #[init]
    fn init() -> u32 {
        return 42;
    }

    // A test which takes the state returned by the init function (optional)
    // This is an async function, it will be executed on the system executor.
    #[test]
    async fn trivial_async(n: u32) {
        assert!(n == 42)
    }
}
}

Again, please refer to the embedded-test documentation for more information.

Running the tests

To run a test, execute from within the crate's directory:

laze build -b <board> test

Tooling

This chapter contains documentation on tooling that Ariel OS provides or integrates.

CoAP

Warning: This documentation is currently ahead of the implementation roadmap.

CoAP is an application layer protocol similar to HTTP in its use (eg. you could POST data onto a resource on a server that is identified by a URL) but geared towards IoT devices in its format and its security mechanisms.

CoAP provides a versatile set of transports (with IETF Proposed Standards for running over UDP including multicast, over TCP and WebSockets, other standards for running over SMS and NB-IoT, and more in development). It relies on proxies to span across transports and to accommodate the characteristics of particular networks, and offers features exceeding the classical REST set such as observation.

Ariel OS supports the use of CoAP for implementing clients, servers or both in a single device. As part of our mission for strong security, we use encrypted CoAP traffic by default as explained below.

Usage: Server side

An example of a CoAP server is provided as examples/coap-server, see [its coap_run() function] for the practical steps.

A CoAP server is created by assembling several resource handlers on dedicated paths: There might be a path /s/0 representing a particular sensor, and a path /fwup for interacting with firmware updates. The CoAP implementation can put additional resources at well-known locations, eg. /.well-known/core for discovery or /.well-known/edhoc for establishing secure connections.

The handler needs to concern itself with security aspects of the request content (eg. file format parsers should treat incoming data as possibly malformed), but the decision whether or not a request is allowed is delegated to an access policy.

Usage: Client side

The example provided as examples/coap-client, which sends a single POST request.

A program that triggers a CoAP request provides1 some components to the CoAP stack before phrasing the actual request:

  • A URL describing the resource, eg. coap://coap.summit.riot-os.org/agenda or coap+tcp://[2001:db8::1]/.well-known/core.

    Note that while the address is printed here as a text URL (and may even be entered as such in code), its memory and transmitted representations are binary components.

  • Optionally, directions regarding how to reach or find the host.

    This is not needed when there are IP literals involved, or DNS is available to resolve names. Directions may be available if the application discovered a usable proxy, or when it is desired to use opt-in discovery mechanisms such as multicast.

  • A policy reference on how to authenticate the server, and which identity to present. This is optional if there is a global policy, or if there is an implied security mechanism for the URL.

1

The components required for a request are not documented as such in the CoAP RFCs, but it is the author's opinion that they are a factual requirement: Implementations may implicitly make decisions on those, but the decisions are still made. At the time of writing, there is an open issue to clarify this in the specifications.

Security

The CoAP stack is configured with server and client policies. The security mechanisms used depend on those selected in the policies.

At this stage, Ariel OS uses three pieces of security components: OSCORE (for symmetric encryption), EDHOC (for key exchange) and ACE (for authentication).

OSCORE/EDHOC/ACE were chosen first because they scale down well to the smallest devices, and because they all have in common that they sit naturally on top of CoAP: Their communication consists of CoAP requests and responses. Thus, they work homogeneously across all CoAP transports, and provide end-to-end security across untrusted proxies.

Alternatives are possible (for instance DTLS, TLS, IPsec or link-layer encryption) but are currently not implemented / not yet supported in Ariel OS.

Server access policy

A policy is configured for the whole server depending on the desired security mechanisms. Examples of described policy entries are:

  • This is a fixed public key, and requests authenticated with that key are allowed to GET and PUT to /limit.
  • The device has a shared secret from its authorization server, with which the authorization server secures the tokens it issues to clients. Clients may perform any action as long as they securely present a token that allows it. For example, a token may allow GET on /limit and PUT on /led/0.
  • Any (even unauthenticated) device may GET /hello/.

Interacting with an Ariel OS CoAP server from the host

A convenient policy (which is the default of Ariel OS's examples) is to grant the user who flashes the device all access on it. When that policy is enabled in the build system, an unencrypted key is created in the developer's state home directory, from where it can be picked up by tools such as aiocoap-client.

Furthermore, when a CoAP server is provisioned through the Ariel OS build system, public keys and their device associations are stored in the developer's state home directory.

Together, these files act in a similar way as the classic UNIX files ~/.netrc and ~/.ssh/id_rsa{,.pub}. They can also double as templates for an application akin to ssh-copy-id in that they enable a server policy like "any device previously flashed on this machine may GET all resources".

Client policies

The policy for outgoing requests can be defined globally or per request.

Examples of policies that can be available are "expect the server to present some concrete public key, use this secret key once the server is verified", "use a token for this audience and scope obtained from that authentication server", "expect the server to present a chain of certificates for its hostname down to a set of root certificates" (which is the default for web browsers), "establish an encrypted connection and trust the peer's key on first use", down to "do not use any encryption".

Available security mechanisms

These components are optional, but enabled by default -- when all are disabled, the only sensible policy that is left is to allow unauthenticated access everywhere. For example, this may make sense on a link layer with tight access control. The components also have internal dependencies: EDHOC can only practically be used in combination with OSCORE; ACE comes with profiles with individual dependencies (eg. using the ACE-EDHOC profile requires EDHOC).

Symmetric encryption: OSCORE

OSCORE (RFC8613) provides symmetric encryption for CoAP requests: It allows clients to phrase their CoAP request, encrypts the parts that a proxy does not need to be aware of2 and sends on the ciphertext in a CoAP request. The server can decrypt the request, process it like any other request (but with the access policy evaluated according to the key's properties), and its response gets encrypted likewise.

Working with symmetric keys requires a lot of care and effort managing keys: assigning the same key twice can have catastrophic consequences, and even recovering from an unplanned reboot is by far not trivial.

Ariel OS does not offer direct access to OSCORE for those reasons, and uses OSCORE's companion mechanisms to set up keys.

Policies are not described in terms of OSCORE keys.

2

Most of the message is encrypted. Noteworthy unencrypted parts are the hostname the request is sent to, the parts that link a response to a request, and housekeeping details such as whether a request is for an observation and thus needs to be kept alive longer.

Key establishment: EDHOC

EDHOC (RFC9528) is a key establishment protocol that uses asymmetric keys to obtain mutual authentication and forward secrecy. In particular, two CoAP devices can run EDHOC over CoAP to obtain key material for OSCORE, which can then be used for fast communication.

Unless ACE or certificate chains are used, the main use of EDHOC in Ariel OS is with raw public keys: Devices (including the host machine) generate a private key, make the corresponding public key known, and then send the public key (or its short ID) along with EDHOC messages to be identified by that public key. This is similar to how SSH keys are commonly used.

Policies described in terms of EDHOC keys include the public key of the peer, which private key to use, and whether our public key needs to be sent as a full public key or can be sent by key ID.

Authorization: ACE

The ACE framework (RFC9200) describes how a trusted service (the Authorization Server, "AS") can facilitate secure connections between devices that are not explicitly configured to be used together. It frequently issues CWTs (CBOR Web Tokens) that are to JWTs (JSON Web Tokens) as CoAP is to HTTP.

The general theme of it is that a client that wants to access some resource on a server under the AS's control first asks the AS for an access token, and then presents that token to the server (called Resource Server / RS in that context).

Clients know the AS by having means to establish some secure connection with it, eg. through EDHOC. Resource Servers know the AS by verifying the tokens they receive from the AS, eg. by having a shared secret with the AS or knowing its signing key.

Details of the process are specified in ACE profiles; apart from those listed here, popular profiles include the DTLS profile and the profiles for group communication.

ACE-OSCORE profile

With the ACE-OSCORE profile (RFC9203), the AS provides a random OSCORE key in the token (which is encrypted for the RS), and sends the token along with the same OSCORE key through its secure connection to the client. Before the client can send OSCORE requests, it POSTs the token to the server over an unprotected connection (the token itself is encrypted), along with a random number and some housekeeping data that go into the establishment of an OSCORE context.

The documentation of the CoAP/ACE-OAuth PoC project describes the whole setup in more detail.

ACE-EDHOC profile

The ACE-EDHOC profile is under development in the ACE working group.

It differs from the ACE-OSCORE profile in that the AS does not provide symmetric key material but only points out the respective peer's public keys.

The token is not sent in plain, but as part of the EDHOC exchange.

Using ACE from the host during development

While full operation of ACE requires having an AS as part of the network, CoAP servers running on Ariel OS can be used in the ACE framework without a live server.

Similar to how an EDHOC key is created on demand on the host, an AS's list of Resource Servers is maintained by default. Tools at the host can then use the locally stored key to create tokens that grant fine-grained permissions on the Ariel OS device.

With the New ACE Workflow developed in ACE, such tokens can also be provisioned into Resource Servers on behalf of clients that are being provisioned. Thus, the offline AS can enable deployed Ariel OS based CoAP servers to accept requests from newly created Ariel OS based CoAP clients without the need for the CoAP client to create a network connection to the host. (Instead, the host needs to find the Resource Server over the network).

.. note: Some more exploration of this workflow will be necessary as to how the client can trigger the AS to re-install (or renew) its token in case the Resource Server retired the token before its expiration. For Ariel OS internal use, AS-provisioned tokens might just be retained longer.

Ferrocene

Ferrocene is the open-source qualified Rust compiler toolchain for safety- and mission-critical. Qualified for automotive, industrial and medical development.

Note: Ferrocene requires a (paid) licence to use.

Ferrocene uses criticalup, its variant of rustup, to manage installing its toolchain and components. Once installed, wrapping regular Rust commands like cargo and rustc with criticalup run enables using the qualified tooling.

The Ariel OS build system seamlessly integrates this for targets supported by Ferrocene (currently all Cortex-M).

Installing Ferrocene

Please refer to the official Ferrocene documentation and the criticalup User Guide for instructions.

Using Ferrocene with Ariel OS

To select the Ferrocene toolchain, enable the ferrocene laze module.

Example:

$ laze -Cexamples/hello-world build --builders nrf52830dk --select ferrocene

Alternatively, add ferrocene to the laze modules of your application.

Glossary

Embassy HAL crates

Embassy crates that implement support for specific MCU families:

Adding Support for a Board

This document serves as a guide as to what is currently needed for adding support for a board/device to Ariel OS.

Feel free to report anything that is unclear or missing!

This guide requires working on your own copy of Ariel OS. You may want to fork the repository to easily upstream your changes later.

Adding Support for a Board

The more similar a board is to one that is already supported, the easier. It is usually best to copy and adapt an existing one.

  • Ensure that the HAL is supported in ariel-os-hal.
  • In laze-project.yml:
    • parent: The MCU's laze context.
    • If the MCU does not have a dedicated software interrupt (SWI), choose one now and set the CONFIG_SWI environment variable.
    • Ensure there is a way to flash the board:
      • If the MCU is supported by probe-rs, specify PROBE_RS_CHIP and PROBE_RS_PROTOCOL.
      • If the board is based on esp, it should inherit the espflash support.
      • If neither of these are supported, please open an issue.
    • Add a builder for the actual board that uses the context from above as parent.

Whether to add an intermediate context or just a builder depends on whether the MCU-specific code can be re-used.

Example for the st-nucleo-f401re board:

contexts:
  # ...
  - name: stm32f401retx
    parent: stm32
    selects:
      - thumbv7em-none-eabi # actually eabihf, but ariel-os doesn't support hard float yet
    env:
      PROBE_RS_CHIP: STM32F401RETx
      PROBE_RS_PROTOCOL: swd
      RUSTFLAGS:
        - --cfg context=\"stm32f401retx\"
      CARGO_ENV:
        - CONFIG_SWI=USART2

builders:
  # ...
  - name: st-nucleo-f401re
    parent: stm32f401retx
  • src/ariel-os-boards/$BOARD: Add a crate that matches the board's name.
    • This crate should inject the board-specific dependencies to the HAL crates.
  • src/ariel-os-boards:
    • Cargo.toml: Add a Cargo feature that matches the board's name.
    • src/lib.rs: Add the board to the dispatch logic.

Adding Support for an MCU from a Supported MCU family

  • In laze-project.yml:
    • Add a context for the MCU (if it does not already exist).
      • parent: The closest Embassy HAL's context.
      • selects: A rustc-target module.

MCU-specific items inside Ariel OS crates are gated behind #[cfg(context = $CONTEXT)] attributes, where $CONTEXT is the MCU's laze context name. These need to be expanded for adding support for the new MCU.

At least the following crates may need to be updated:

  • The Ariel OS HAL crate for the MCU family.
  • ariel-os-storage
  • ariel-os-embassy

It may also be needed to introduce a new crate in src/ariel-os-chips.

The ariel-os-chips crate should eventually not be needed anymore.

Adding Support for an Embassy HAL/MCU family

As of this writing, Ariel OS supports most HALs that Embassy supports, including esp-hal, nrf, rp, and stm32, but excluding std and wasm.

The steps to add support for another Embassy supported HAL are:

  • src/ariel-os-hal:
    • Cargo.toml: Add a dependency on the Embassy HAL crate.
    • src/lib.rs: Add the Ariel OS HAL to the dispatch logic.
  • Create a new Ariel OS HAL crate (similar to ariel-os-nrf).

Adding Support for a Processor Architecture

Each rustc target needs its own module in laze-project.yml. If the processor architecture that is being added is not listed yet, you will need to take care of that.

Example:

modules:
  # ...
  - name: thumbv6m-none-eabi
    depends:
      - cortex-m
    env:
      global:
        RUSTC_TARGET: thumbv6m-none-eabi
        CARGO_TARGET_PREFIX: CARGO_TARGET_THUMBV6M_NONE_EABI
        RUSTFLAGS:
          - --cfg armv6m

The variables RUSTC_TARGET and CARGO_TARGET_PREFIX need to be adjusted. Add --cfg $HAL as needed.

Chances are that if you need to add this, you will also have to add support for the processor architecture to ariel-os-bench, ariel-os-rt, ariel-os-threads.

Coding Conventions

Item Order in Rust Modules

Items SHOULD appear in Rust modules in the following order, based on the one used by rust-analyzer:

  1. Inner doc comment
  2. Inner attributes
  3. Unconditional—i.e., not feature-gated—bodyless modules
  4. Conditional—i.e., feature-gated—bodyless modules
  5. Unconditional imports from the following:
    1. std/alloc/core
    2. External crates (including crates from the same workspace)
    3. Current crate, paths prefixed by crate
    4. Current module, paths prefixed by self
    5. Super module, paths prefixed by super
    6. Re-exports—i.e., pub imports not used in the module
  6. Conditional imports from the same
  7. Const items
  8. Static items
  9. Other items

TODO: type aliases before other items?

TODO: how to organize type definitions w.r.t. related logic?

Imports

Imports from the same crate with the same visibility MUST be merged into a single use statement.

Imports from Re-exports

When using whole-crate re-exports from ariel_os::reexports, two imports SHOULD be used: one to bring the re-exported crate into the scope, and the other one to import the required items from that crate, as it it were a direct dependency of the crate, as follows:

#![allow(unused)]
fn main() {
use ariel_os::reexports::embassy_usb;
use embassy_usb::class::hid::HidReaderWriter;
}

Comments

Doc Comments

All public items listed in the documentation—i.e., not marked with #[doc(hidden)]—SHOULD be documented.

Doc comments MUST use the line comment style, not the block style.

Doc comments MUST be written in third person present, not in the imperative mood, as recommended by RFC 1574. Each sentence in doc comments—including the first one, before an empty line—SHOULD end with a period. For instance, instead of:

#![allow(unused)]
fn main() {
/// Get the underlying value
}

write:

#![allow(unused)]
fn main() {
/// Returns the underlying value.
}

More generally, use the std docs as inspiration.

When possible—i.e., when items are in scope—items mentioned in the documentation MUST be linked to (see C-LINK). This is useful for readers, to quickly access the mentioned item, but it also helps prevent the docs from lagging behind, as broken links are tested for in CI, making it easy to spot renamed or removed items.

unsafe Code

When unsafe is used, a SAFETY comment MUST be added, in the style supported by Clippy.

TODO: enforce it in CI

Naming Conventions

Names SHOULD adhere to the official API guidelines.

TODO: how to name error types/error enum variants (CannotDoSth vs DoingSth)?

Dependencies

If the same dependency is used in multiples crates within the workspace, that dependency SHOULD be specified in the workspace's Cargo.toml file and workspace crates should import them from there.

Ariel OS Code of Conduct

Ariel OS, like so many other free and open source software projects, is made up of a mixed group of professionals and volunteers from all over the world, working on every aspect of our goals - including teaching, steering, and connecting people.

We want diversity to be one of our greatest strengths, but it can also lead to communication issues and unhappiness. This is why we ask people to adhere to a few ground rules. They apply equally to founders, maintainers, contributors and those seeking help and guidance.

This is not meant to be an exhaustive list of things you are not allowed to do. We rather would like you to think of it as a guide to enrich our community and the technical community in general with new knowledge and perspectives by allowing everyone to participate.

This code of conduct applies to all spaces managed by the Ariel OS community. This includes Matrix, the mailing lists, our GitHub projects, face to face events, and any other forums created by the community for communication within the community. In addition, violations of this code outside these spaces may also affect a person's ability to participate within them.

If you believe someone is violating the code of conduct, we ask that you report it by emailing conduct@ariel-os.org. For more details please see our Reporting Guidelines.

  • Be friendly and patient.
  • Be welcoming. We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
  • Be considerate. Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
  • Be respectful. Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Ariel OS community should be respectful when dealing with other members as well as with people outside the Ariel OS community.
  • Be careful in the words that you choose. We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
    • Violent threats or language directed against another person.
    • Discriminatory jokes and language.
    • Posting sexually explicit or violent material.
    • Posting (or threatening to post) other people's personally identifying information ("doxing").
    • Personal insults, especially those using racist or sexist terms.
    • Unwelcome sexual attention.
    • Advocating for, or encouraging, any of the above behavior.
    • Repeated harassment of others. In general, if someone asks you to stop, then stop.
  • When we disagree, try to understand why. Disagreements, both social and technical, happen all the time and Ariel OS is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of Ariel OS comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.

Text based on the Code of Conduct of the Django community.

Questions?

If you have questions, please feel free to contact us.

Reporting Guidelines

If you believe someone is violating the code of conduct we ask that you report it to us by emailing conduct@ariel-os.org.

All reports will be kept confidential. In some cases we may determine that a public statement will need to be made. If that's the case, the identities of all victims and reporters will remain confidential unless those individuals instruct us otherwise.

If you believe anyone is in physical danger, please notify appropriate law enforcement first. If you are unsure what law enforcement agency is appropriate, please include this in your report and we will attempt to notify them.

If you are unsure whether the incident is a violation, or whether the space where it happened is covered by this Code of Conduct, we encourage you to still report it. We would much rather have a few extra reports where we decide to take no action, rather than miss a report of an actual violation. We do not look negatively on you if we find the incident is not a violation. And knowing about incidents that are not violations, or happen outside our spaces, can also help us to improve the Code of Conduct or the processes surrounding it.

In your report please include:

  • Your contact info (so we can get in touch with you if we need to follow up)
  • Names (real, nicknames, or pseudonyms) of any individuals involved. If there were other witnesses besides you, please try to include them as well.
  • When and where the incident occurred. Please be as specific as possible.
  • Your account of what occurred. If there is a publicly available record (e.g. a mailing list archive or a public Matrix logger) please include a link.
  • Any extra context you believe existed for the incident.
  • If you believe this incident is ongoing.
  • Any other information you believe we should have.

What happens after you file a report?

You will receive an email from one of the core community members as soon as possible. We promise to acknowledge receipt within 24 hours (and will aim for much quicker than that).

They will review the incident and determine:

  • What happened.
  • Whether this event constitutes a code of conduct violation.
  • Who the bad actor was.
  • Whether this is an ongoing situation, or if there is a threat to anyone's physical safety.

If this is determined to be an ongoing incident or a threat to physical safety, their immediate priority will be to protect everyone involved. This means we may delay an "official" response until we believe that the situation has ended and that everyone is physically safe.

Once the working group has a complete account of the events they will make a decision as to how to response. Responses may include:

  • Nothing (if we determine no violation occurred).
  • A private reprimand from us to the individual(s) involved.
  • A public reprimand.
  • An imposed vacation (i.e. asking someone to "take a week off" from a mailing list or Matrix).
  • A permanent or temporary ban from some or all Ariel OS spaces (mailing lists, Matrix, etc.)
  • A request for a public or private apology.

We'll respond within one week to the person who filed the report with either a resolution or an explanation of why the situation is not yet resolved.

Once we've determined our final action, we'll contact the original reporter to let them know what action (if any) we'll be taking. We'll take into account feedback from the reporter on the appropriateness of our response, but we don't guarantee we'll act on it.

Reference

These reporting guidelines were adapted from the Django reporting guidelines