Interaction Between BOLOS and Apps

Since BOLOS is designed based on a single-task model where only a single app runs at any given time, an application is independently responsible for a lot of the things a typical OS would do. These things include managing hardware like the device screen, buttons, timer, etc. as well as handling all I/O with peripherals. However, there are many instances where a BOLOS application has to request the operating system to perform a certain operation. This is done using a syscall.

When an application performs a syscall, the Secure Element switches to Supervisor mode and the OS performs the requested task before returning control back to the application, in User mode. All syscalls have a wrapper function in the SDK that can be used to invoke them. A syscall may be used to access hardware accelerated cryptographic primitives (most of these functions are defined in include/cx.h in the SDKs), to perform low-level I/O operations (like receiving / transmitting to the MCU), or to access cryptographic secrets managed by BOLOS (for example, to derive a node from the BIP 32 master node).

Error Model

If you are familiar with C programming, you will be used to error codes as the default error model. However, when programming in the embedded world, this traditional model reaches its limits, and can quickly overcomplicate large codebases. Therefore, we’ve implemented a try / catch system that supports nesting (direct or transitive) using the setjmp and longjmp API to facilitate writing robust code.

Here is an example of a typical try / catch / finally construct:

BEGIN_TRY {
    TRY {
        // Perform some operation that may throw an error using THROW(...)
    } CATCH_OTHER(e) {
        // Handle error
    } FINALLY {
        // Always executed before continuing control flow
    }
} END_TRY;

However there is a single constraint to be aware of with our try / catch system: a TRY clause must always be closed in the appropriate way. This means that if using a return, break, continue or goto statement that jumps out of the TRY clause you MUST manually close it, or it could lead to a crash of the application in a later THROW. A TRY clause can be manually closed using CLOSE_TRY. Using CLOSE_TRY is only necessary when jumping out of a TRY clause, jumping out of a CATCH or FINALLY clause is allowed (but still, be careful you’re not in a CATCH nested in a TRY).

You should use the error codes defined in the SDKs wherever possible (see EXCEPTION, INVALID_PARAMETER, etc. in os.h). If you decide to use custom error codes, never use an error code of 0.

Developers should avoid creating a new try context wherever possible in order to reduce code size and stack usage. Preferably, an application should only have a single top-level try context at the application entry point (in main()).

Syscall Requirements

BOLOS is based on an exception model for error reporting, therefore, it expects the application to call the BOLOS API using this mechanism. If an API function is called from outside a TRY context, then the BOLOS call is denied.

Here is a valid way to call a system entry point:

BEGIN_TRY {
    TRY {
        cx_hash_sha512(...);
    } FINALLY {}
} END_TRY;

However, as mentioned above, it is preferred to use as few try contexts as possible (not one per syscall). A single, top-level try context can be used to catch any exception thrown by any syscall performed by the application.