Skip to content

Architecture

Germán Luis Aracil Boned edited this page Apr 6, 2026 · 1 revision

Architecture

Message Flow

Every operation follows the same path:

Client → Interface → Core Router → ACL Check → Module Handler → Response
    ┌─────────┐     ┌─────────┐     ┌─────────┐
    │   CLI   │     │  HTTP   │     │   SSH   │
    └────┬────┘     └────┬────┘     └────┬────┘
         │               │               │
         └───────┬───────┴───────┬───────┘
                 │               │
         ┌───────▼───────────────▼───────┐
         │         Core Engine           │
         │  ┌─────────────────────────┐  │
         │  │    Path Router (FNV1a)  │  │
         │  │    O(1) hash lookup     │  │
         │  └────────────┬────────────┘  │
         │               │               │
         │  ┌────────────▼────────────┐  │
         │  │    ACL Check            │  │
         │  │    user.groups ∩        │  │
         │  │    path.labels ≠ ∅      │  │
         │  └────────────┬────────────┘  │
         │               │               │
         │  ┌────────────▼────────────┐  │
         │  │   Module Dispatch       │  │
         │  │   setjmp + handler()    │  │
         │  └─────────────────────────┘  │
         └───────────────────────────────┘
                 │       │       │
         ┌───────┘       │       └───────┐
    ┌────▼────┐    ┌─────▼─────┐   ┌────▼────┐
    │ mod_web │    │ mod_cache │   │ mod_iot │
    └─────────┘    └───────────┘   └─────────┘

Core Components

Component File Purpose
Main src/main.c Entry point, arg parsing, signal handling, event loop
Path Router src/core/core_path.c FNV-1a hash table, path registration, wildcard fallback
Module Loader src/core/core_module.c dlopen/dlsym, reference counting, hot-reload
Message System src/core/core_message.c Message creation, routing, method dispatch
ACL Engine src/core/core_auth.c Users, groups, labels, token management
Event System src/core/core_events.c Pub/Sub, pattern matching, ACL-controlled subscriptions
Storage src/core/core_storage.c Multi-backend fan-out writes, priority reads
Store src/core/core_store.c File-based persistence (always enabled)
Config src/core/core_config.c Config parsing, /config/* path interface
Wire Protocol src/core/core_wire.c PORTAL02 binary serialization for federation
Hash Table src/core/core_hashtable.c Generic hash table used by router and cache
Logging src/core/core_log.c Multi-level logging, file rotation
Handlers src/core/core_handlers.c Built-in internal path handlers
Instance src/core/portal_instance.c Core state management, multi-instance support
Pub/Sub src/core/core_pubsub.c Event subscription and emission engine

Path Routing

Paths are hashed with FNV-1a for O(1) lookup:

uint32_t hash = 2166136261u;      // FNV offset basis
for (const char *p = path; *p; p++) {
    hash ^= (uint8_t)*p;
    hash *= 16777619u;            // FNV prime
}
return hash & (table_size - 1);   // table_size is power of 2

Lookup order:

  1. Exact match in hash table
  2. Walk path backwards stripping segments, try wildcard * at each level
  3. Return 404 NOT_FOUND

Label-Based ACL

           User              Path
    ┌──────────────┐   ┌──────────────┐
    │ groups:      │   │ labels:      │
    │  - admin     │   │  - admin     │
    │  - ops       │   │  - security  │
    └──────┬───────┘   └──────┬───────┘
           │                  │
           └────────┬─────────┘
                    │
              intersection
              {admin} ≠ ∅
                    │
              ACCESS GRANTED

Special rules:

  • User with group * → superuser, access everything
  • Path with no labels → public, anyone can access
  • Path with label admin → only users with group admin

Crash Isolation

static jmp_buf crash_buf;
static volatile sig_atomic_t crash_signal = 0;

static void crash_handler(int sig) {
    crash_signal = sig;
    longjmp(crash_buf, 1);
}

// In message dispatch:
signal(SIGSEGV, crash_handler);
signal(SIGFPE,  crash_handler);

if (setjmp(crash_buf) == 0) {
    rc = mod->handle(core, msg, resp);   // module code runs here
} else {
    // module crashed — core is safe
    resp->status = 500;
    mod->stats.crashes++;
    core_log(LOG_ERROR, "Module %s crashed: signal %d", mod->name, crash_signal);
}

signal(SIGSEGV, SIG_DFL);
signal(SIGFPE,  SIG_DFL);

Storage Architecture

    Module SET /config/key = value
                    │
         ┌──────────▼──────────┐
         │   Storage Engine    │
         │   (fan-out write)   │
         └──┬──────┬──────┬────┘
            │      │      │
    ┌───────▼┐ ┌───▼────┐ ┌▼────────┐
    │  File  │ │ SQLite │ │PostgreSQL│
    │(always)│ │  (WAL) │ │  (async) │
    └────────┘ └────────┘ └──────────┘

    Module GET /config/key
                    │
         ┌──────────▼──────────┐
         │   Storage Engine    │
         │   (priority read)   │
         └──────────┬──────────┘
                    │
         PostgreSQL → SQLite → File
         (first success wins)

Event System

Three subscription patterns:

  • Exact: /events/iot/device_added — matches only this event
  • Wildcard: /events/iot/* — matches all events under /events/iot/
  • Global: * — matches every event in the system

Events are ACL-controlled. A subscriber can only receive events whose labels intersect with the subscriber's groups.

Wire Protocol (PORTAL02)

Binary format for federation:

┌────────┬────────┬──────────┬───────────┬──────────────┐
│ MAGIC  │ VERSION│ MSG_TYPE │ BODY_LEN  │    BODY      │
│ 4 bytes│ 2 bytes│ 1 byte   │ 4 bytes   │ variable     │
│"PRT\0" │ 0x0002 │ 0x01=msg │ uint32 BE │ serialized   │
└────────┴────────┴──────────┴───────────┴──────────────┘

Key authentication: SHA-256 challenge-response handshake, shared federation key.

Clone this wiki locally