Skip to content

Ioto Safe Runtime (R)

Ioto is built upon the Safe Runtime (R), a secure, high-performance C runtime library designed specifically for embedded IoT applications. This foundational layer insulates Ioto from the underlying platform and enables portability across operating systems and hardware.

Ioto Architecture

Design Goals

The Safe Runtime is built around these core principles:

  1. Security First — Mitigate buffer overflows, memory corruption, and other common C vulnerabilities
  2. Embedded Optimization — Ultra-compact footprint suitable for resource-constrained devices
  3. Simplicity — Null-tolerant APIs that reduce boilerplate error checking
  4. Portability — Cross-platform support via OS abstraction layer (Linux, macOS, Windows, ESP32, FreeRTOS, VxWorks)
  5. Concurrency — Lightweight fiber coroutines instead of heavyweight OS threads
  6. Performance — Zero-copy operations and optimized data structures

Architecture Overview

The Safe Runtime follows a layered architecture with modular components:

┌─────────────────────────────────────────────────────────────┐
│                    Application Layer                        │
│         (IoT Agent, Web Server, MQTT Client, etc.)          │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                  Safe Runtime (R) Library                   │
│ ┌─────────────┐  ┌──────────────┐  ┌────────────────────┐   │
│ │   Fiber     │  │   Event      │  │   I/O & Network    │   │
│ │ Coroutines  │  │    Loop      │  │   (Socket, File)   │   │
│ └─────────────┘  └──────────────┘  └────────────────────┘   │
│ ┌─────────────┐  ┌──────────────┐  ┌────────────────────┐   │
│ │   Memory    │  │     Data     │  │   Safe Strings     │   │
│ │ Management  │  │  Structures  │  │   & Buffers        │   │
│ └─────────────┘  └──────────────┘  └────────────────────┘   │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │        Platform Abstraction Layer (osdep)               │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                Operating System / RTOS                      │
│         (Linux, macOS, Windows, FreeRTOS, etc.)             │
└─────────────────────────────────────────────────────────────┘

Core Subsystems

The Safe Runtime provides these key subsystems:

SubsystemDescription
Runtime CoreInitialization, lifecycle management, and service coordination
Memory ManagementCentralized allocation with automatic failure handling
Fiber SystemCooperative multitasking with lightweight coroutines
Event SystemTime-based and I/O event scheduling
Data StructuresLists, hash tables, red-black trees, dynamic buffers
String OperationsSafe string functions that prevent buffer overflows
I/O LayerFiber-aware file and socket operations with TLS support
Platform AbstractionOS-specific implementations unified under common API

Key Security Features

The Safe Runtime replaces standard C library functions with secure alternatives:

Standard CSafe RuntimeProtection
strlen()slen()NULL tolerance
strcpy()scopy(dest, size, src)Bounds checking
strcat()scat(dest, size, src)Bounds checking
strcmp()scmp(s1, s2)NULL tolerance
sprintf()sfmt(fmt, ...)Dynamic allocation
strdup()sclone(str)NULL returns empty string
malloc()/free()rAlloc()/rFree()Centralized failure handling

Additional Security Features:

  • Buffer Overflow Prevention — Bounds-checked string operations
  • Memory Safety — Centralized allocation prevents leaks and double-frees
  • Stack Protection — Guard pages and overflow detection for fiber stacks
  • TLS Support — OpenSSL and MbedTLS integration for secure communications

TLS Session Resumption

For faster TLS reconnections to the same server, cache and reuse TLS sessions:

c
// After successful connection, save session
RSession *session = rGetTlsSession(sock);

// On reconnect, apply cached session before connecting
rSetTlsSession(newSock, session);
rConnectSocket(newSock, host, port, 0);

// When done with cached session
rFreeTlsSession(session);

Session resumption avoids the full TLS handshake, reducing connection time significantly for repeated connections to the same server.

Memory Footprint

The runtime is optimized for embedded devices:

ConfigurationSizeUse Case
Minimal~50KBCore runtime + memory + strings only
Typical IoT~200KBFull runtime with fibers, events, sockets, TLS
Full~400KBAll features enabled including OpenSSL

Centralized Memory Allocator

The Safe Runtime uses a centralized memory allocation strategy with a global error handler. This design simplifies application code by eliminating the need for individual NULL checks after every allocation.

How It Works

All memory allocations go through the rAlloc() family of functions:

FunctionDescription
rAlloc(size)Allocate memory, aligned to 8-byte boundary
rAllocZeroed(size)Allocate and zero-initialize
rAllocType(Type)Allocate structure with type safety
rRealloc(ptr, size)Resize allocation
rFree(ptr)Free memory (NULL-safe)

When memory allocation fails, the runtime invokes a centralized memory handler rather than returning NULL. By default, this handler terminates the application — a safe behavior for embedded systems where memory exhaustion typically indicates an unrecoverable state.

Benefits

Simplified Code: Applications don't need to check every allocation for NULL:

c
// Traditional C - requires NULL check after every allocation
char *buf = malloc(1024);
if (buf == NULL) {
    // handle error
}

// Safe Runtime - no NULL check needed
char *buf = rAlloc(1024);
// If this returns, allocation succeeded

Single Point of Failure Handling: All allocation failures are handled in one place, making it easier to implement consistent error recovery or logging.

Custom Memory Handler

Applications can install a custom memory handler using rSetMemHandler() to implement alternative failure strategies such as logging, cleanup, or graceful degradation:

c
static void myMemHandler(size_t size) {
    rError("memory", "Allocation failed for %zu bytes", size);
    // Perform cleanup or attempt recovery
    exit(1);
}

// Install custom handler
rSetMemHandler(myMemHandler);

Ioto Types

The Ioto Agent uses an operating system dependent layer called "osdep".

By including "osdep.h", you gain access to these cross-platform types. Some types are implemented natively on some platforms and others are provided by "osdep".

Quick Tour

Here are some primitive data types:

c
bool   b;
int8   i8;
int16  i16;
int32  i32;
int64  i64;
uint8  u8;
uint16 u16;
uint32 u32;
uint64 u64;
schar  c;      //  Signed char
uchar  c;      //  Unsigned char
cchar  c;      //  Const char
cuchar  c;     //  Const unsigned char

Other useful types:

c
ssize   length;     //  64-bit signed length
Time    time;       //  Time in milliseconds since Jan 1, 1970
Ticks   ticks;      //  Elapsed time in system millisecond ticks (never goes backwards)

For a full list of the osdep types, see: OSDEP API

Return Values

Many Ioto APIs return an integer value that is zero for success and otherwise set to a negative error code.

Here are the return codes used by the Ioto runtime:

c
#define R_ERR_OK              0
#define R_ERR_BASE            -1
#define R_ERR                 -2
#define R_ERR_ABORTED         -3
#define R_ERR_ALREADY_EXISTS  -4
#define R_ERR_BAD_ACK         -5
#define R_ERR_BAD_ARGS        -6
#define R_ERR_BAD_DATA        -7
#define R_ERR_BAD_FORMAT      -8
#define R_ERR_BAD_HANDLE      -9
#define R_ERR_BAD_NULL        -10
#define R_ERR_BAD_REQUEST     -11
#define R_ERR_BAD_RESPONSE    -12
#define R_ERR_BAD_SESSION     -13
#define R_ERR_BAD_STATE       -14
#define R_ERR_BAD_SYNTAX      -15
#define R_ERR_BAD_TYPE        -16
#define R_ERR_BAD_VALUE       -17
#define R_ERR_BUSY            -18
#define R_ERR_CANT_ACCESS     -19
#define R_ERR_CANT_ALLOCATE   -20
#define R_ERR_CANT_COMPLETE   -21
#define R_ERR_CANT_CONNECT    -22
#define R_ERR_CANT_CREATE     -23
#define R_ERR_CANT_DELETE     -24
#define R_ERR_CANT_FIND       -25
#define R_ERR_CANT_INITIALIZE -26
#define R_ERR_CANT_LOAD       -27
#define R_ERR_CANT_OPEN       -28
#define R_ERR_CANT_READ       -29
#define R_ERR_CANT_WRITE      -30
#define R_ERR_DELETED         -31
#define R_ERR_MEMORY          -32
#define R_ERR_NETWORK         -33
#define R_ERR_NOT_CONNECTED   -34
#define R_ERR_NOT_INITIALIZED -35
#define R_ERR_NOT_READY       -36
#define R_ERR_READ_ONLY       -37
#define R_ERR_TIMEOUT         -38
#define R_ERR_TOO_MANY        -39
#define R_ERR_WONT_FIT        -40
#define R_ERR_WOULD_BLOCK     -41
#define R_ERR_MAX             -42

NULL Tolerance

Ioto adopts a technique call NULL tolerance where APIs, wherever possible, tolerate NULL arguments and attempt to do something reasonable with their arguments, rather than crashing the applications.

The standard C library is not NULL tolerant. If you pass a NULL pointer to routines such as as free() or strlen() your program will crash.

The Ioto memory allocator is NULL tolerant when calling rFree. Similarly, Ioto provides a safe string library that will accept NULL arguments wherever possible. In the case of strlen, Ioto provides slen() which will return zero if passed a NULL pointer.