Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
Vital Channel Communication Architecture
  • See also
    vital_channel_topologies for common redundancy patterns (2oo2, 2oo3, hot standby, mirroring)
  • See also
    ipc_guide for transport selection, configuration, and implementation

Overview

  • Vital Channels provide redundancy voting for safety-critical inter-process communication.
  • A vital channel wraps multiple underlying transport channels (IPC, shared memory, TCP, etc.)
  • and applies voting logic to ensure data integrity across distributed processes.
  • The key design principle: Transport Independence. The vital_channel module does not
  • depend on any specific IPC implementation—it uses transport-agnostic callbacks to invoke
  • send/receive operations on the underlying channels.

Communication Flow (2oo2 Example)

  • In a typical railway system using 2-out-of-2 voting:
  • WEST Process A (Vital Compute)
  • |
  • v
  • [Vital Channel Layer]
  • | (send via callbacks)
  • +---> Channel 0 (Inter-Process Communication or shared memory to Process B)
  • |
  • +---> Channel 1 (Inter-Process Communication or shared memory to Process C)
  • WEST Process B (Vital Verify) WEST Process C (Non-Vital Service)
  • | |
  • v v
  • [Receive from Channel 0] [Receive from Channel 1]
  • |
  • v
  • [Vital Channel Voting]
  • |
  • +-- If Ch0 == Ch1 -> AGREE, return data
  • |
  • +-- If Ch0 != Ch1 -> DISAGREE, trigger SAFE-STATE

Transport Backends

  • A vital channel operates on opaque void* channel handles. The application provides
  • two callback functions to specify HOW to communicate:
  • - backend_send(): Invoked by vital_channel_send() for each redundant channel
  • - backend_recv(): Invoked by vital_channel_receive() for each redundant channel
  • These callbacks abstract away the transport mechanism:
  • | Transport | Backend Implementation |
  • |--------—|---------------------—|
  • | IPC Request-Reply | Call sapi_ipc_rr_send() / sapi_ipc_rr_receive() |
  • | IPC Pub-Sub | Call sapi_ipc_pubsub_publish() / sapi_ipc_pubsub_receive() |
  • | Shared Memory | Direct memcpy to/from shared memory region |
  • | TCP | Call socket send()/recv() via wrapper |
  • | Custom Transport | User-defined send/recv functions |
  • This design enables a single vital_channel implementation to work with ANY transport.

Send Semantics (Atomic Broadcast)

  • When sapi_channel_send() is called:
  • 1. Broadcast Phase: Invoke backend_send() for each redundant channel
  • - All channels receive the same data
  • - Ordering is deterministic (channel 0, then 1, then 2, etc.)
  • 2. Atomicity: ALL-or-NOTHING guarantee
  • - If ANY channel fails or times out, return error (partial send never visible)
  • - If ALL channels succeed, return SAPI_STATUS_OK
  • - This prevents inconsistent system state (one receiver gets data, another doesn't)
  • 3. Health Tracking: Update per-channel send counts and error counters

Receive Semantics (Voting)

  • When sapi_channel_receive() is called:
  • 1. Collection Phase: Invoke backend_recv() for each redundant channel
  • - Store results in static voting buffers (one per channel)
  • - Track timeouts and errors per-channel
  • 2. Voting Phase: Apply quorum logic
  • - 2oo2 (both must agree): Compare channels 0 and 1. If equal -> AGREED. If different -> DISAGREED.
  • - 2oo3 (majority wins): Find majority among 3 channels. If tie or no majority -> INSUFFICIENT_QUORUM.
  • - NMR (N out of M): Generalized voting using quorum_size threshold.
  • 3. Disagreement Handling
  • - Log disagreement (if log_disagreements=true)
  • - Call optional on_disagreement callback
  • - CRITICAL: Trigger SAFE-STATE automatically via SAPI_SAFESTATE macro
  • - Return SAPI_STATUS_HARDWARE_FAULT to caller
  • 4. Agreement Handling
  • - Copy agreed data to output buffer
  • - Return SAPI_STATUS_OK

Quorum Requirements

  • A vital channel requires sufficient healthy channels to achieve quorum:
  • - 2oo2: Need >= 2 healthy channels (both must participate)
  • - 2oo3: Need >= 2 healthy channels (tolerate 1 fault)
  • - NMR: Need >= quorum_size healthy channels (tolerate M - quorum_size faults)
  • If quorum is lost (e.g., both channels in 2oo2 become unhealthy), all operations
  • return SAPI_STATUS_HARDWARE_FAULT with voting result = SAPI_VOTING_INSUFFICIENT_QUORUM.

Health Monitoring

  • The vital_channel module tracks per-channel metrics:
  • c
  • typedef struct {
  • uint32_t send_count; // Successful sends
  • uint32_t send_error_count; // Failed sends
  • uint32_t receive_count; // Successful receives
  • uint32_t receive_error_count; // Failed receives (timeout/error)
  • uint32_t disagreement_count; // Times this channel disagreed with majority
  • bool is_healthy; // Current health state (true = healthy)
  • sapi_status_t last_error; // Last error code
  • } sapi_channel_health_t;
  • Applications can query this via sapi_channel_get_health() to:
  • - Detect early signs of channel degradation (rising error counters)
  • - Isolate faulty channels (high disagreement_count)
  • - Implement predictive fault detection (before safe-state is triggered)

Application Integration

  • Typical application flow:
  • c
  • // Step 1: Create transport-specific channels (e.g., Inter-Process Communication request-reply)
  • sapi_ipc_rr_server_t ch0, ch1;
  • sapi_ipc_rr_server_create(&ch0, &config);
  • sapi_ipc_rr_server_create(&ch1, &config);
  • void *channels[2] = { &ch0, &ch1 };
  • // Step 2: Define backend callbacks (how to invoke transport operations)
  • sapi_status_t backend_send(void *ch, const void *data, size_t size) {
  • return sapi_ipc_rr_send((sapi_ipc_rr_server_t*)ch, data, size);
  • }
  • sapi_status_t backend_recv(void *ch, void *data, size_t size, uint32_t timeout) {
  • return sapi_ipc_rr_receive((sapi_ipc_rr_server_t*)ch, data, size, timeout);
  • }
  • // Step 3: Initialize vital channel with callbacks
  • sapi_channel_config_t vital_config = {
  • .voting_strategy = SAPI_VOTING_2OO2,
  • .channel_timeout_ms = 1000,
  • .log_disagreements = true,
  • .backend_send = backend_send, // Transport-specific callback
  • .backend_recv = backend_recv, // Transport-specific callback
  • };
  • sapi_channel_storage_t vital;
  • sapi_channel_init(&vital, &vital_config, channels, 2);
  • // Step 4: Use vital channel for redundant communication
  • sapi_channel_send(&vital, &cmd, sizeof(cmd)); // Atomic broadcast
  • sapi_channel_receive(&vital, &result, sizeof(result), NULL, NULL); // Voting
  • // Step 5: Monitor health (watchdog role)
  • sapi_channel_health_t health;
  • sapi_channel_get_health(&vital, 0, &health);
  • if (health.disagreement_count > THRESHOLD) {
  • // Early warning: channel drifting, consider isolation
  • }

Safety Properties

  • The vital_channel module guarantees (for CENELEC EN 50128 SIL 3/4 contexts):
  • - Atomicity: Sends are all-or-nothing; no partial broadcast state
  • - Voting Integrity: Disagreements are ALWAYS detected and escalated to safe-state
  • - Silent Failure Prevention: Timeouts and errors are tracked; no silent data corruption
  • - Deterministic: No dynamic allocation, bounded buffers, time-bounded operations
  • - MISRA Compliant: No recursion, no uninitialized variables, explicit casts only

Message Size Limits

  • The vital_channel implementation uses static voting buffers:
  • c
  • #define SAPI_VITAL_CHANNEL_MAX_MESSAGE_SIZE 256
  • Messages larger than 256 bytes return SAPI_STATUS_RESOURCE_EXHAUSTED.
  • Applications needing larger messages should:
  • - Use chunked transfer over multiple messages
  • - Or reduce message size by refactoring data structures
  • - Do NOT use dynamic buffering (MISRA violation)

Usage Examples

2-out-of-2 Voting (Perfect Agreement Required)

  • Use case: Critical signal transmission where BOTH redundant processes must agree.
  • Example: Train speed command from central interlocking to on-board computer.
  • c
  • config.voting_strategy = SAPI_VOTING_2OO2;
  • // Both channels must return identical data, or SAFE-STATE triggers

2-out-of-3 Voting (Tolerate 1 Fault)

  • Use case: Three redundant processes; majority vote wins. Tolerate 1 Byzantine fault.
  • Example: Odometer reading from 3 CCTV-based vision systems (different sensors may drift).
  • c
  • config.voting_strategy = SAPI_VOTING_2OO3;
  • // If 2 channels agree, that's the result. If all 3 disagree, SAFE-STATE.

N-out-of-M Voting

  • Use case: Generalized voter for N processes, requires M of N to agree.
  • Example: 5 GNSS receivers; require 3 to agree within epsilon range.
  • c
  • config.voting_strategy = SAPI_VOTING_NMR;
  • config.quorum_size = 3; // At least 3 out of 5 must agree

Known Limitations

  • - Static Message Size: 256 byte limit (MISRA-driven, no dynamic allocation)
  • - No Byzantine Fault Tolerance: Designed for benign failures (timeout, corruption);
  • does not defend against malicious/Byzantine channels
  • - Simple Voting Only: Exact byte-for-byte comparison; no approximate/fuzzy voting
  • - No Automatic Channel Isolation: Health metrics are tracked but automatic isolation
  • is left to the application/watchdog layer
  • */