Chapter 19 — Alias Mapping: Global Table

On a CAN bus, every OpenLCB node is identified by a 48-bit Node ID, but CAN 2.0B frames only provide 12 bits for source addressing. The alias mapping table bridges this gap by maintaining a registry of 12-bit CAN alias to 48-bit Node ID pairs. This chapter covers the global alias mapping table implemented in alias_mappings.h and alias_mappings.c.

19.1 The alias_mapping_t Structure

Each entry in the alias mapping table is an alias_mapping_t:

typedef struct alias_mapping_struct {
    node_id_t node_id;        // Permanent 48-bit Node ID
    uint16_t alias;           // Temporary 12-bit CAN alias (0x001-0xFFF)
    uint8_t is_duplicate : 1; // Set by ISR when another node claims this alias
    uint8_t is_permitted : 1; // Set after successful login (AMD transmitted)
} alias_mapping_t;
FieldTypeDescription
node_idnode_id_t (uint64_t)The permanent 48-bit OpenLCB Node ID. Zero means the slot is empty.
aliasuint16_tThe temporary 12-bit CAN alias (valid range 0x001 - 0xFFF). Zero means empty.
is_duplicate1-bit flagSet by the CAN RX ISR when a frame arrives from another node using the same alias. The main loop reads this flag to trigger conflict resolution.
is_permitted1-bit flagSet after the node successfully transmits its AMD frame, meaning the alias is officially claimed.

19.2 The alias_mapping_info_t Container

All mapping entries are held in a single static container:

typedef struct alias_mapping_info_struct {
    alias_mapping_t list[ALIAS_MAPPING_BUFFER_DEPTH]; // All registered mappings
    bool has_duplicate_alias;                          // Fast-check flag
} alias_mapping_info_t;

The ALIAS_MAPPING_BUFFER_DEPTH macro defaults to USER_DEFINED_NODE_BUFFER_DEPTH, so there is one slot per virtual node. The has_duplicate_alias flag is a fast-check optimization: when set, at least one entry has is_duplicate set, and the main loop should scan the list to find and resolve the conflict.

Why a separate fast-check flag?

Checking every entry's is_duplicate field on every main loop iteration is wasteful when conflicts are rare. The single has_duplicate_alias boolean lets the main loop skip the scan entirely in the common case. The ISR sets it; the main loop clears it after resolution.

19.3 API Functions

FunctionDescription
AliasMappings_initialize() Zeros all entries and clears the duplicate flag. Call once at startup before any node operations.
AliasMappings_register(alias, node_id) Registers a CAN alias / Node ID pair. Finds the first empty slot or updates an existing entry with the same Node ID. Returns a pointer to the entry, or NULL if the buffer is full or parameters are out of range.
AliasMappings_unregister(alias) Removes the entry matching the given alias, clearing all four fields. Safe to call with an alias that does not exist.
AliasMappings_find_mapping_by_alias(alias) Bidirectional lookup: returns the entry for the given 12-bit alias, or NULL if not found.
AliasMappings_find_mapping_by_node_id(node_id) Bidirectional lookup: returns the entry for the given 48-bit Node ID, or NULL if not found.
AliasMappings_set_has_duplicate_alias_flag() Signals the main loop that an incoming message used one of our reserved aliases. Does NOT automatically trigger reallocation.
AliasMappings_clear_has_duplicate_alias_flag() Clears the duplicate flag after the main loop has resolved the conflict.
AliasMappings_flush() Clears all mappings and resets all flags. Functionally identical to initialize() but intended for runtime use (system reset, test teardown).
AliasMappings_get_alias_mapping_info() Returns a pointer to the internal alias_mapping_info_t. Intended for diagnostics and testing.

19.4 Registration and Lookup

The register function enforces one alias per Node ID. If a Node ID is already registered, calling AliasMappings_register() with a new alias silently replaces the old alias. This is the correct behavior during conflict recovery, where a node must adopt a new alias.

// Registration algorithm:
// 1. Validate alias is in range 0x001-0xFFF
// 2. Validate node_id is in range 0x000000000001-0xFFFFFFFFFFFF
// 3. Scan all ALIAS_MAPPING_BUFFER_DEPTH entries
// 4. On first empty slot (alias==0) or matching Node ID: store and return
// 5. If no slot found: return NULL (buffer full)
Caller Must Check for NULL

All lookup and register functions return NULL when the entry is not found or the buffer is full. Callers must always check the return value before dereferencing.

19.5 Duplicate Alias Detection

Duplicate detection follows a two-phase pattern dictated by the ISR/main-loop architecture:

sequenceDiagram participant ISR as CAN RX ISR participant Table as Alias Mapping Table participant Main as Main Loop ISR->>Table: Frame arrives with alias 0x3AB Table->>Table: Find entry with alias 0x3AB Note over Table: Entry found: set is_duplicate = true Table->>Table: Set has_duplicate_alias = true ISR-->>Main: (returns to main loop) Main->>Table: Check has_duplicate_alias? Note over Main: Flag is true -- scan list Main->>Table: Find entry with is_duplicate == true Note over Main: Found node with alias 0x3AB Main->>Main: Reset node: unregister old alias,
re-seed, restart login Main->>Table: AliasMappings_clear_has_duplicate_alias_flag()

19.6 Alias Lifecycle

An alias transitions through several states during its lifetime on the CAN bus:

stateDiagram-v2 [*] --> Generate: Power-on / conflict Generate --> Check: LFSR produces 12-bit alias Check --> Reserved: CID7, CID6, CID5, CID4 sent
Wait 200ms, no objection Check --> Generate: Objection received
during CID or wait Reserved --> Defined: RID sent, then AMD sent
is_permitted = true Defined --> Released: AMR sent or
alias conflict detected Released --> [*] note right of Generate Seed derived from Node ID. LFSR advances on each retry. end note note right of Check Four CID frames broadcast 12 bits of Node ID each. 200ms wait for collisions. end note note right of Defined Normal operation. Alias is in the mapping table with is_permitted = true. end note
StateCAN FrameTable Action
Generate-AliasMappings_register(alias, node_id) -- reserves the slot
Check (CID)CID7, CID6, CID5, CID4Entry exists with is_permitted = false
ReserveRIDNo table change; alias is now claimed
DefineAMDis_permitted = true
ReleaseAMRAliasMappings_unregister(alias) -- clears the slot

19.7 Thread Safety

The alias mapping table is shared between the CAN RX interrupt (which sets is_duplicate and has_duplicate_alias) and the main loop (which reads and clears these flags, registers and unregisters entries). This makes it one of the critical shared data structures described in Chapter 6.

ContextOperations
CAN RX ISRReads entries (find by alias), sets is_duplicate, sets has_duplicate_alias
Main LoopRegisters/unregisters entries, clears flags, performs lookups

The lock_shared_resources() / unlock_shared_resources() mechanism must protect accesses from the main loop when the ISR might modify the same data. On bare-metal platforms, this typically means disabling CAN RX interrupts during main-loop table operations.

19.8 Source Files

FilePurpose
drivers/canbus/alias_mappings.hPublic API declarations
drivers/canbus/alias_mappings.cImplementation -- single static alias_mapping_info_t instance, linear scan
drivers/canbus/can_types.hType definitions for alias_mapping_t and alias_mapping_info_t
← Prev: Ch 18 — Broadcast Time Next: Ch 20 — Listener Aliases →