This repository contains the core implementation of the EnduroSat Function Protocol (FP) service, which EnduroSat hardware modules use to communicate with each other and with the ground segment. The FP service provides a robust, session-aware communication framework with advanced features like ARM-and-FIRE operations for critical commands.
FP is used together with code generated from Franca IDL (FIDL) interface definitions by EnduroSat's macchiato code generator. macchiato is available to EnduroSat customers on request from EnduroSat support.
The FP service provides:
- π SerDes mechanism for user-defined APIs with automatic code generation
- π‘ FP-specific session handling with timeout management and tracking
- π ARM-and-FIRE mechanism for critical operations requiring confirmation
- π Multi-transport support (CSP, RS485, local communication)
- π‘οΈ Thread-safe operation with configurable locking mechanisms
- β‘ Zero-copy operation for optimal performance
- π Comprehensive error handling and status reporting
- π Built-in diagnostics and session metrics
The ARM-and-FIRE mechanism provides a two-stage execution process for critical operations:
- Two-stage execution prevents accidental command execution
- Data integrity validation using hash comparison between ARM and FIRE stages
- Automatic timeout (15 minutes default) with session cleanup
- Explicit confirmation required before executing dangerous commands
- Cancellation support via DISARM requests
- Client sessions for request/response tracking with timeout handling
- Server sessions for ARM-and-FIRE operations with state persistence
- Automatic cleanup of expired sessions to prevent resource leaks
- Configurable session limits and tracking metrics
- Thread-safe session isolation between different communication contexts
- CSP (CubeSat Space Protocol) integration for space communications
- RS485 serial communication for ground equipment
- Local in-process communication for internal modules
- Extensible architecture for additional transport protocols
- Context-specific configuration for each transport type
- Context-specific locking mechanisms to minimize contention
- Registry protection for FIDL interface management
- Zero-copy operation for communication buffers
- Efficient session tracking with minimal overhead
- Lock-free operations where possible
The implementation is designed to integrate with host systems that provide:
- C11-compliant compiler (GCC, MSVC, TI MSP430)
- Standard library support for basic types and functions
- Packed structure support for protocol definitions
- Multi-threaded environment (RTOS or OS-hosted preferred)
- Timer services for timeout management and session tracking
- Mutex/locking primitives for thread synchronization
- Event signaling mechanisms for synchronous operations
- Hash calculation capabilities (CRC16 or similar algorithm)
- Memory management for session storage
- Trace/logging infrastructure (optional but recommended)
All external interfaces are declared in the config/fp_arch.h.template header and must be implemented by the host system. Template implementations are provided for common scenarios.
fp/
βββ π inc/ # Public interface headers
β βββ fp_disp.h # Main dispatcher interface
β βββ fp_disp_client.h # Client-side operations
β βββ fp_disp_server.h # Server-side operations
β βββ fp_disp_session_mgr.h # Session management
β βββ fp_registry.h # FIDL interface registry
β βββ fp_proto.h # Protocol definitions and utilities
β βββ fp_defs.h # Common type definitions
βββ π src/ # Implementation files
β βββ fp_disp.c # Core dispatcher logic
β βββ fp_disp_client.c # Client request handling
β βββ fp_disp_server.c # Server request processing
β βββ fp_disp_session_mgr.c # Session lifecycle management
β βββ fp_registry.c # FIDL registry implementation
β βββ fp_proto.c # Protocol utilities
β βββ fp_internal.c # Internal helper functions
βββ π config/ # Configuration templates
β βββ fp_arch.h.template # Architecture interface template
β βββ fp_config.h.template # System configuration template
β βββ fp_config_user.h.template # User configuration header
β βββ fp_config_user.c.template # User configuration implementation
βββ π test/ # Unit tests and test utilities
βββ π diagrams/ # PlantUML documentation diagrams
β βββ fp_regular_dispatch.puml # Regular request/response flow
β βββ fp_arm_and_fire.puml # ARM-and-FIRE sequence
β βββ fp_system_architecture.puml # System architecture overview
β βββ fp_session_lifecycle.puml # Session management lifecycle
β βββ fp_error_handling.puml # Error scenarios and handling
β βββ fp_message_format.puml # Protocol message structure
β βββ fp_transport_integration.puml # Transport layer patterns
β βββ fp_session_class_hierarchy.puml # FP session class hierarchy
β βββ generate_svg.sh # Diagram generation script
β βββ π svg/ # Generated SVG diagrams
βββ LICENSE # EnduroSat Software License
βββ LICENSES/ # License texts referenced by SPDX headers
βββ CONTRIBUTING.md # How to contribute
βββ README.md # This comprehensive documentation
The FP service uses a context-driven architecture to eliminate global state dependencies and enable multi-transport operation. The only global component is the FIDL registry, which maintains a singleton pattern for interface definitions shared across all FP-bound interfaces.
- Dispatcher (
fp_disp) - Central routing and frame processing - Session Manager (
fp_disp_session_mgr) - Lifecycle and timeout management - Registry (
fp_registry) - FIDL interface registration and lookup - Protocol Handler (
fp_proto) - Message format and attribute processing - Transport Abstraction - Pluggable communication layer support
The service operates on a zero-copy principle where communication buffers provided to fp_disp_execute are transparently passed to application handlers. Data copying only occurs for synchronous FP requests where responses arrive in different thread contexts.
The FP message header (fp_msg_hdr_t) is a packed structure that contains the following fields:
PACK_STRUCT_BEGIN
typedef struct
{
uint16_t iface_id; /**< Interface ID (matching the FIDL interface ID) */
uint32_t func_id; /**< Function ID with embedded attribute bits */
uint16_t cmd_id; /**< Command ID (unique ID used to match requests and responses) */
uint8_t status; /**< Status code (used by responses; requests set to 0) */
} fp_msg_hdr_t;
PACK_STRUCT_ENDField Descriptions:
iface_id: Identifies the FIDL interface (0-65535)func_id: Contains both function ID and attribute bits (see below)cmd_id: Unique identifier for request/response matchingstatus: Operation result code (see Status Codes)
The func_id field uses a 32-bit value with embedded attribute bits:
31 24 20 0
| | | |
MS bit [ x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x ] LS bit 0 (32 bits)
----------------------- ---------------------------------------
| |
Function Attribute Bits (12 bits) Function ID (20 bits)
Bit Layout:
- Bits 0-19: Actual function ID (1,048,576 possible functions)
- Bits 20-31: Attribute bits for frame type and special operations
The upper bits of the func_id field contain special attribute bits:
| Bit Position | Mask Value | Constant Name | Description |
|---|---|---|---|
| 31 | 0x80000000 | RESP_FUNC_ID_MASK |
Response frame indicator |
| 30 | 0x40000000 | ARMREQ_FUNC_ID_MASK |
ARM request frame |
| 29 | 0x20000000 | FIREREQ_FUNC_ID_MASK |
FIRE request frame |
| 28 | 0x10000000 | DISARMREQ_FUNC_ID_MASK |
DISARM request frame |
| 27 | 0x08000000 | BROADCAST_FUNC_ID_MASK |
Broadcast frame indicator |
| 26-20 | 0x07F00000 | Reserved | Future use |
The protocol supports different frame types defined in fp_frame_type_t:
| Frame Type | Value | Constant | Description |
|---|---|---|---|
| REQ | 0 | FP_FRAME_TYPE_REQ |
Standard Function Protocol request |
| REQ_ARM | 1 | FP_FRAME_TYPE_REQ_ARM |
ARM request frame (first stage) |
| REQ_FIRE | 2 | FP_FRAME_TYPE_REQ_FIRE |
FIRE request frame (second stage) |
| REQ_DISARM | 3 | FP_FRAME_TYPE_REQ_DISARM |
DISARM request frame (cancel) |
| RESP | 4 | FP_FRAME_TYPE_RESP |
Standard Function Protocol response |
| BROADCAST | 5 | FP_FRAME_TYPE_BROADCAST |
Broadcast frame (one-way notification) |
Response messages use the status field to indicate the result of an operation:
Status code origin: Not all status codes travel over the wire. There are two distinct kinds:
Wire codes β carried in the
statusfield of actual FP frames exchanged between a server and a client. These include success codes (0x20-0x2F) and server error codes (0x50-0x5F). A server populates these values and a client reads them from the received frame.Internal codes β generated locally by the FP dispatcher and never appear in any frame on the wire. These are used to communicate internal state back to the calling user implementation: client-side errors (0x10-0x1F, e.g. bad arguments or send failure), timeout/resource errors (0x30-0x3F), and protocol violation errors (0x40-0x4F, e.g. unexpected frame type or mismatched size). The dispatcher synthesises these values and delivers them via callback status or return value.
| Status Code | Value | Constant | Description |
|---|---|---|---|
| NO ERROR | 0x20 | FP_CMD_STATUS_NOERROR |
Operation executed successfully |
| Status Code | Value | Constant | Description |
|---|---|---|---|
| INVALID ARGS | 0x10 | FP_CMD_STATUS_INVARGS |
Argument validity check failed |
| SEND ERROR | 0x11 | FP_CMD_STATUS_SENDERR |
Frame could not be sent |
| Status Code | Value | Constant | Description |
|---|---|---|---|
| TIMEOUT | 0x30 | FP_CMD_STATUS_TIMEOUT |
Server did not respond within timeout |
| NO MEMORY | 0x31 | FP_CMD_STATUS_NO_MEM |
Not enough memory to track request |
| Status Code | Value | Constant | Description |
|---|---|---|---|
| UNEXPECTED FRAME TYPE | 0x40 | FP_CMD_STATUS_UNEXP_FRM_TYPE |
Response frame arrived with an unexpected frame type (e.g. not a RESP frame) |
| UNEXPECTED FRAME SIZE | 0x41 | FP_CMD_STATUS_UNEXP_FRM_SIZE |
Response frame arrived with a size that does not match the FIDL-declared size |
These codes are generated by the FP dispatcher itself β they are never sent over the wire by a server. When a protocol violation is detected:
- Async path: the user callback is invoked with the violation status and
p_data == NULL. - Sync path:
fp_disp_client_send_reqreturns the violation status and the caller's response buffer is zeroed. When the FIDL-declared response size is known (the registered-client route,fp_disp_client_handle_resp), the zeroing and the success copy never write more than that size into the caller buffer, even though the pool-wide slot capacity (resp_data_max_size) can be much larger. On the unmatched route (fp_disp_client_handle_resp_unmatched: any response whose interface has no registered client serdes) no typed size exists, so an unbounded caller is still cleared or copied up toresp_data_max_size. Callers that can land there must usefp_disp_client_send_req_bounded. The caller buffer is normally one typed response struct on the caller stack.
| Status Code | Value | Constant | Description |
|---|---|---|---|
| NOT SUPPORTED | 0x50 | FP_CMD_STATUS_FUNC_NOT_SUPPORTED |
Server doesn't support the operation |
| ARMED | 0x51 | FP_CMD_STATUS_ARMED |
Request is armed but not yet executed |
| DISARMED | 0x52 | FP_CMD_STATUS_DISARMED |
Request is disarmed |
| ATTR ERROR | 0x53 | FP_CMD_STATUS_REQ_ATTR_ERR |
Request has unexpected attribute bits |
| DATA INTEGRITY | 0x54 | FP_CMD_STATUS_DATA_INTEGRITY_ERR |
Data fields changed between ARM/FIRE |
Status Code Categories:
- 0x00-0x0F: Reserved for future use
- 0x10-0x1F: Client-side errors
- 0x20-0x2F: Success codes
- 0x30-0x3F: Timeout and resource errors
- 0x40-0x4F: Protocol violation codes (internal, dispatcher-generated)
- 0x50-0x5F: Server-side errors
- 0x60-0xFF: Reserved for future use
The ARM-and-FIRE mechanism provides a robust two-stage triggering for critical operations that require explicit confirmation before execution.
Client β Server: ARM request with operation data
Server β Client: ARMED status (if validation passes)
Server Actions:
- Validates the request parameters
- Calculates hash of request data using
fp_arch_calc_hash() - Creates server session to track the armed request
- Stores request hash in session for later verification
- Starts 15-minute timeout timer
- Responds with
FP_CMD_STATUS_ARMED
Client β Server: FIRE request with identical operation data
Server β Client: Operation result (if hash matches)
Server Actions:
- Finds existing server session for the request
- Calculates hash of new request data
- Compares new hash with stored hash
- If hashes match: Executes operation and closes session
- If hashes differ: Returns
FP_CMD_STATUS_DATA_INTEGRITY_ERR
Client β Server: DISARM request
Server β Client: DISARMED status
Server Actions:
- Finds and closes the server session
- Stops the timeout timer
- Responds with
FP_CMD_STATUS_DISARMED
Automatic Disarm:
- Default timeout: 15 minutes (
ARMED_REQ_TOUT_S) - Configurable via
fp_config.h - Session automatically closed on timeout
- No notification sent to client on timeout
Session Tracking:
// Session tracking runs in a background thread
fp_disp_session_mgr_track_sessions(&sys_context);The ARM-and-FIRE mechanism ensures data integrity through:
- Hash Validation: Request data must be identical between ARM and FIRE
- Session Isolation: Each armed request has its own session
- Timeout Protection: Prevents indefinite resource holding
- Explicit Confirmation: Requires deliberate FIRE action
Critical Operations:
- Satellite attitude changes
- Payload power control
- Communication system resets
- Firmware updates
- Mission-critical commands
Benefits:
- Prevents accidental execution
- Provides confirmation step
- Detects data corruption
- Enables operation cancellation
Broadcast frames provide a mechanism for server-to-client one-way notifications that do not require or expect a response.
One-Way Communication:
- Broadcasts are sent from server to client only
- No response is expected or generated
- Client requests cannot use broadcast frame type (rejected with
FP_CMD_STATUS_INVARGS)
Routing Behavior:
- Broadcasts route to CLIENT-type FIDL handlers (same routing as response frames)
- This reflects the semantic direction: server β client
- If no matching CLIENT-type handler is found, the broadcast is silently ignored (no error response sent)
Command ID Generation:
- Each broadcast generates a unique
cmd_idusingfp_internal_gen_cmd_id() - Command IDs are generated atomically for thread safety
- Unlike request/response pairs, broadcast
cmd_idvalues are not matched to any client session
Use fp_disp_server_send_bcast() to send broadcast notifications:
bool fp_disp_server_send_bcast(fp_dispatch_info_t *p_disp_ctx,
const uint16_t iface_id,
const uint32_t func_id,
uint8_t *const p_bcast,
const uint32_t bcast_size);Parameters:
p_disp_ctx: Dispatch context containing system context and transport callbacksiface_id: FIDL interface ID for the broadcastfunc_id: Function ID (bare, without frame type bits)p_bcast: Pointer to broadcast message buffer (must be at leastsizeof(fp_msg_hdr_t))bcast_size: Size of broadcast message in bytes (minimum:sizeof(fp_msg_hdr_t)for zero-argument broadcasts)
Returns: true if broadcast was sent successfully, false otherwise
Note: Zero-argument broadcasts (broadcasts with no payload beyond the FP message header) are fully supported. For such broadcasts, bcast_size should be sizeof(fp_msg_hdr_t).
FIDL Registration:
- Register CLIENT-type FIDL interfaces to receive broadcasts
- The FP service library routes broadcasts through the same
fp_disp_execute()mechanism used for responses - Broadcast callbacks receive full
fp_dispatch_info_tcontext
API Usage:
Use fp_disp_client_handle_bcast() to handle received broadcast frames:
void fp_disp_client_handle_bcast(fp_dispatch_info_t *p_disp_ctx,
const uint32_t bcast_type_size,
fp_generic_broadcast_handler_t p_bcast_handler);This function:
- Validates the frame type is
FP_FRAME_TYPE_BROADCAST - Validates the broadcast size matches the expected type size
- Invokes the user-registered broadcast handler callback
Note: The bcast_type_size parameter specifies the expected size of the broadcast message. For zero-argument broadcasts (broadcasts with no payload beyond the header), this should be sizeof(fp_msg_hdr_t).
Generated Code Responsibilities:
The code generation layer (macchiato) is responsible for:
- Subscription Management: Determining which broadcasts a client wants to receive
- Callback Routing: Mapping broadcast function IDs to user-registered callbacks via
fp_disp_client_handle_bcast() - Static Storage: Maintaining subscription state and callback registrations
- Filtering: Ignoring unwanted broadcasts at the application level
- Type Safety: Deserializing broadcast payloads to appropriate data structures
Example:
// User-defined broadcast handler
void user_telemetry_handler(void *p_disp_ctx, uint8_t *p_bcast_data)
{
telemetry_broadcast_t *p_telemetry = (telemetry_broadcast_t *)p_bcast_data;
printf("Telemetry: temp=%d, voltage=%d\n",
p_telemetry->temperature, p_telemetry->voltage);
}
// Generated FIDL callback for broadcast function
void fidl_telemetry_broadcast_callback(fp_dispatch_info_t *p_disp_ctx)
{
// Use broadcast handler API
fp_disp_client_handle_bcast(p_disp_ctx,
sizeof(telemetry_broadcast_t),
&user_telemetry_handler);
}
// FIDL registration
fidl_func_t telemetry_funcs[] = {
{ .func_id = TELEMETRY_BCAST_FUNC_ID,
.p_on_func_cbk = &fidl_telemetry_broadcast_callback }
};
fidl_if_t telemetry_client_if = {
.version = 1,
.gen_type = FIDL_TYPE_CLIENT,
.fidl_if_id = TELEMETRY_IFACE_ID,
.func_count = 1,
.p_func_list = telemetry_funcs
};
fp_registry_add_fidl_if(&telemetry_client_if);Zero-Argument Broadcast Example:
// User-defined handler for zero-argument broadcast (e.g., heartbeat)
void user_heartbeat_handler(void *p_disp_ctx, uint8_t *p_bcast_data)
{
// No payload to process - just the notification itself is meaningful
fp_msg_hdr_t *p_hdr = (fp_msg_hdr_t *)p_bcast_data;
printf("Heartbeat received from iface_id=%u\n", p_hdr->iface_id);
}
// Generated FIDL callback for zero-argument broadcast
void fidl_heartbeat_broadcast_callback(fp_dispatch_info_t *p_disp_ctx)
{
// For zero-argument broadcasts, size is just the header
fp_disp_client_handle_bcast(p_disp_ctx,
sizeof(fp_msg_hdr_t),
&user_heartbeat_handler);
}Typical Broadcast Scenarios:
- Telemetry updates (periodic sensor data)
- System status notifications (mode changes, warnings)
- Event announcements (threshold crossings, state transitions)
- Heartbeat/keepalive messages
- Multi-client notifications (same broadcast to multiple clients)
When to Use Broadcasts vs. Responses:
- Use broadcasts for unsolicited notifications that don't correspond to a specific client request
- Use responses for replies to client requests where the client expects a response
The FP service maintains sophisticated session tracking for both client and server operations.
Purpose: Track outgoing requests and match responses
Lifecycle:
- Created when sending request
- Tracks timeout for response
- Matches incoming response by
cmd_id - Closed when response received or timeout
Key Fields:
typedef struct {
fp_disp_session_base_t base; // Common session data (first field)
fp_generic_response_handler_t p_user_handler; // Response callback (async only)
fp_req_type_t req_type; // SYNC or ASYNC
void *p_session_event; // Event for sync waiting
uint8_t *p_resp_data; // Response buffer (sync only)
uint32_t resp_data_max_size; // Pool-wide response slot capacity
uint32_t resp_buf_user_max; // Caller buffer capacity (0 = pool-wide)
uint32_t resp_data_size; // Bytes delivered for the last sync reply
} fp_disp_client_session_t;Purpose: Track ARM-and-FIRE operations
Lifecycle:
- Created on ARM request
- Stores request hash and parameters
- Validates FIRE request data
- Closed on FIRE, DISARM, or timeout
Key Fields:
typedef struct {
fp_disp_session_base_t base; // Common session data (first field)
fp_generic_request_handler_t p_user_handler; // Request handler run on FIRE
uint16_t req_data_hash; // Hash of ARM request data
} fp_disp_server_session_t;Limits (configurable in fp_config.h):
#define MAX_CLIENT_SESSIONS_COUNT ((uint16_t)5)
#define MAX_SERVER_SESSIONS_COUNT ((uint16_t)5)
#define MAX_FIDL_COUNT ((uint16_t)5) // FIDL interfaces in the registry
#define ARMED_REQ_TOUT_S ((uint16_t)(15 * 60)) // 15 minutesThe system tracks session statistics:
typedef struct {
uint32_t active_session_cnt; // Currently active sessions
uint32_t open_session_cnt; // Total sessions opened
uint32_t closed_session_cnt; // Total sessions closed
uint32_t timed_out_session_cnt; // Sessions that timed out
} fp_disp_session_metrics_t;The config/fp_arch.h.template file defines the platform-specific interface that must be implemented by the host system. This interface abstracts away platform dependencies and enables the FP service to run on different operating systems and hardware platforms.
These functions provide thread synchronization for synchronous FP requests:
void fp_arch_init_event(void **pp_event);
bool fp_arch_wait_for_event(void *p_event, const uint32_t timeout_ms);
void fp_arch_signal_event(void *p_event);Purpose: Enable blocking/unblocking of threads waiting for FP responses.
Implementation Example (CMSIS-RTOS2):
// Called before every synchronous request is sent: create the event once,
// and clear a flag left by a response that arrived after an earlier timeout.
void fp_arch_init_event(void **pp_event) {
if (*pp_event == NULL) {
*pp_event = osEventFlagsNew(NULL);
fp_arch_assert(*pp_event != NULL);
}
(void)osEventFlagsClear((osEventFlagsId_t)*pp_event, 0x01U);
}
// The response may arrive before the caller starts waiting: the flag stays
// set until this wait consumes it (osFlagsWaitAny clears it on return).
bool fp_arch_wait_for_event(void *p_event, const uint32_t timeout_ms) {
uint32_t ticks = (uint32_t)(((uint64_t)timeout_ms * osKernelGetTickFreq()) / 1000U);
uint32_t flags = osEventFlagsWait((osEventFlagsId_t)p_event, 0x01U,
osFlagsWaitAny, ticks);
return ((flags & osFlagsError) == 0U) && ((flags & 0x01U) != 0U);
}
void fp_arch_signal_event(void *p_event) {
(void)osEventFlagsSet((osEventFlagsId_t)p_event, 0x01U);
}These functions provide mutual exclusion for protecting shared data:
- Registry Lock: Global lock for FIDL registry access
- Section Locks: Context-specific locks for session data
bool fp_arch_lock_registry(void);
void fp_arch_unlock_registry(void);
bool fp_arch_lock_section(void *const p_lock);
void fp_arch_unlock_section(void *const p_lock);Implementation Example (CMSIS-RTOS2):
static osMutexId_t g_registry_mutex = NULL;
// Call once at start-up, before any thread uses FP: creating the mutex
// lazily inside fp_arch_lock_registry() races when two threads lock first.
void my_platform_fp_init(void) {
g_registry_mutex = osMutexNew(NULL);
fp_arch_assert(g_registry_mutex != NULL);
}
bool fp_arch_lock_registry(void) {
osStatus_t status = osMutexAcquire(g_registry_mutex, osWaitForever);
return (status == osOK);
}
void fp_arch_unlock_registry(void) {
if (g_registry_mutex != NULL) {
osMutexRelease(g_registry_mutex);
}
}
bool fp_arch_lock_section(void *const p_lock) {
osMutexId_t mutex = (osMutexId_t)p_lock;
if (mutex == NULL) return false;
osStatus_t status = osMutexAcquire(mutex, osWaitForever);
return (status == osOK);
}
void fp_arch_unlock_section(void *const p_lock) {
osMutexId_t mutex = (osMutexId_t)p_lock;
if (mutex != NULL) {
osMutexRelease(mutex);
}
}These functions provide timeout management for sessions:
void fp_arch_start_timer(fp_arch_timer_handle_t *const p_timer_hnd);
void fp_arch_stop_timer(fp_arch_timer_handle_t *const p_timer_hnd);
bool fp_arch_is_timer_elapsed(fp_arch_timer_handle_t *const p_timer_hnd,
const uint32_t elapse_time);Implementation Example (System Tick):
typedef uint32_t fp_arch_timer_handle_t;
// 0 means "not running", so a start at tick 0 (right after boot) is stored as 1.
void fp_arch_start_timer(fp_arch_timer_handle_t *const p_timer_hnd) {
if (p_timer_hnd != NULL) {
uint32_t now = osKernelGetTickCount();
*p_timer_hnd = (now != 0U) ? now : 1U;
}
}
void fp_arch_stop_timer(fp_arch_timer_handle_t *const p_timer_hnd) {
if (p_timer_hnd != NULL) {
*p_timer_hnd = 0;
}
}
bool fp_arch_is_timer_elapsed(fp_arch_timer_handle_t *const p_timer_hnd,
const uint32_t elapse_time) {
if (p_timer_hnd == NULL || *p_timer_hnd == 0) {
return true; // Timer not started or stopped
}
uint32_t current_tick = osKernelGetTickCount();
uint64_t elapsed_ms = ((uint64_t)(current_tick - *p_timer_hnd) * 1000U) / osKernelGetTickFreq();
return elapsed_ms >= elapse_time;
}This function provides data integrity validation for ARM-and-FIRE operations. Its purpose is to avoid buffering the request data during the first stage of execution, only to check the data again in the second stage. The hash function can be as simple as a running sum over the data bytes or a more complex hash calculation. Some examples follow...
uint16_t fp_arch_calc_hash(const uint8_t *const p_data, const uint32_t data_sz);Implementation Example (CRC16):
uint16_t fp_arch_calc_hash(const uint8_t *const p_data, const uint32_t data_sz) {
if (p_data == NULL || data_sz == 0) {
return 0;
}
uint16_t crc = 0xFFFF;
for (uint32_t i = 0; i < data_sz; i++) {
crc ^= p_data[i];
for (uint8_t bit = 0; bit < 8; bit++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001; // CRC16-ANSI polynomial
} else {
crc >>= 1;
}
}
}
return crc;
}Alternative Implementation (Simple Checksum):
uint16_t fp_arch_calc_hash(const uint8_t *const p_data, const uint32_t data_sz) {
if (p_data == NULL || data_sz == 0) {
return 0;
}
uint16_t sum = 0;
for (uint32_t i = 0; i < data_sz; i++) {
sum += p_data[i];
sum = (sum << 1) | (sum >> 15); // Rotate left
}
return sum;
}These functions provide debugging and error handling support:
void fp_arch_assert(const bool cond);
void fp_arch_break_assert(const bool cond);cond is the condition that must hold, as with the standard assert(): the
service calls e.g. fp_arch_assert(NULL != p_ctx). A false cond is the
violation.
Implementation Example:
void fp_arch_assert(const bool cond) {
if (!cond) {
// Log error and halt system
printf("FP ASSERT FAILED\n");
while (1) { /* Halt */ }
}
}
void fp_arch_break_assert(const bool cond) {
if (!cond) {
#if defined(DEBUG) && defined(__ARM_ARCH)
__BKPT(0); // ARM Cortex-M only: stop in the debugger (CMSIS core header)
#endif
fp_arch_assert(cond);
}
}The template provides trace macros that should be mapped to your logging system:
#define FP_ARCH_TRACE_ERROR(format, ...) your_log_error(format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_WARN(format, ...) your_log_warn(format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_INFO(format, ...) your_log_info(format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_FATAL(format, ...) your_log_fatal(format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_DEBUG(format, ...) your_log_debug(format, ##__VA_ARGS__)Example Implementation:
#include "your_logging_system.h"
#define FP_ARCH_TRACE_ERROR(format, ...) LOG_ERROR("[FP] " format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_WARN(format, ...) LOG_WARN("[FP] " format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_INFO(format, ...) LOG_INFO("[FP] " format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_FATAL(format, ...) LOG_FATAL("[FP] " format, ##__VA_ARGS__)
#define FP_ARCH_TRACE_DEBUG(format, ...) LOG_DEBUG("[FP] " format, ##__VA_ARGS__)The template handles different compilers for packed structures:
// GCC and compatible compilers
#ifdef __GNUC__
#define PACK_STRUCT_BEGIN _Pragma("pack(push, 1)")
#define PACK_STRUCT_END _Pragma("pack(pop)")
// Microsoft Visual C++
#elif defined(_MSC_VER)
#define PACK_STRUCT_BEGIN __pragma(pack(push, 1))
#define PACK_STRUCT_END __pragma(pack(pop))
// TI MSP430 Compiler
#elif defined(__TI_COMPILER_VERSION__)
#define PACK_STRUCT_BEGIN _Pragma("pack(push, 1)")
#define PACK_STRUCT_END _Pragma("pack(pop)")
#endifWhen implementing the fp_arch.h interface:
- Event Management: Implement thread synchronization primitives
- Mutex Support: Provide locking mechanisms for thread safety
- Timer Services: Implement timeout tracking for sessions
- Hash Function: Choose appropriate algorithm for data integrity
- Assert Handling: Define error handling and debugging behavior
- Trace Macros: Map to your logging/debugging system
- Compiler Support: Ensure packed structure definitions work
- Testing: Verify all functions work correctly in your environment
- Use OS-provided synchronization primitives
- Consider priority inheritance for mutexes
- Implement proper timeout handling
- Account for tick resolution in timers
- Implement simple spinlocks for synchronization
- Use system tick counter for timing
- Consider interrupt safety
- Minimize memory usage
- Ensure atomic operations where needed
- Consider cache coherency for shared data
- Use appropriate memory barriers
- Test thoroughly for race conditions
Step 1: Copy Configuration Templates
cp config/fp_arch.h.template your_project/fp_arch.h
cp config/fp_config.h.template your_project/fp_config.h
cp config/fp_config_user.h.template your_project/fp_config_user.h
cp config/fp_config_user.c.template your_project/fp_config_user.cStep 2: Implement Platform Interface
Edit fp_arch.h to implement the required functions for your platform:
// Example for CMSIS-RTOS2
#include "cmsis_os2.h"
void fp_arch_init_event(void **pp_event) {
// Implement event initialization
}
bool fp_arch_wait_for_event(void *p_event, const uint32_t timeout_ms) {
// Implement event waiting
return false;
}
// ... implement the other functions declared in fp_arch.h
// (see "Architecture Interface Implementation" above)Step 3: Configure System Parameters
Edit fp_config.h to set system limits:
#define MAX_CLIENT_SESSIONS_COUNT ((uint16_t)10)
#define MAX_SERVER_SESSIONS_COUNT ((uint16_t)5)
#define MAX_FIDL_COUNT ((uint16_t)5)
#define ARMED_REQ_TOUT_S ((uint16_t)(15 * 60))All examples below assume that the underlying FP transport layer is properly configured and functional.
Client Side (Sending Requests):
#include "fp_disp_client.h"
#include "fp_config_user.h"
// Initialize FP system
fp_config_user_init();
// Setup dispatch context
fp_dispatch_info_t disp_ctx = { 0 };
fp_config_user_assign_sys_context(&disp_ctx, FP_CONFIG_SYS_CTX_TYPE_CSP);
// Request and response frames start with the FP header (fp_msg_hdr_t), which
// the service fills in; the payload follows it.
uint8_t request_buffer[64];
uint8_t response_buffer[64];
// Send synchronous request; the response copy never exceeds the given capacity
fp_cmd_status_t status = fp_disp_client_send_req_bounded(
&disp_ctx,
0x1234, // interface ID
0x5678, // function ID
FP_FRAME_TYPE_REQ, // regular request
request_buffer, // request frame (header + payload)
sizeof(request_buffer), // request size, header included
response_buffer, // response buffer
sizeof(response_buffer), // response buffer capacity
NULL, // sync request (no callback)
5000 // timeout in milliseconds
);
if (status == FP_CMD_STATUS_NOERROR) {
// disp_ctx.data_size holds the number of response bytes delivered
}Server Side (Handling Requests):
#include "fp_disp_server.h"
#include "fp_registry.h"
#define MY_IFACE_ID ((uint16_t)0x1234)
#define MY_FUNC_ID ((uint32_t)0x5678)
// Typed frames start with the FP header (normally generated from the FIDL)
PACK_STRUCT_BEGIN
typedef struct { fp_msg_hdr_t hdr; uint32_t value; } my_req_t;
typedef struct { fp_msg_hdr_t hdr; uint32_t result; } my_resp_t;
PACK_STRUCT_END
// Request handler: p_disp_ctx is the request's fp_dispatch_info_t,
// p_untyped_req the request frame (header + payload)
static void my_request_handler(void *p_disp_ctx, void *p_untyped_req) {
const my_req_t *p_req = (const my_req_t *)p_untyped_req;
my_resp_t resp = { .result = p_req->value * 2U };
// The service fills in the response header (IDs, cmd_id, status)
(void)fp_disp_server_send_resp((fp_dispatch_info_t *)p_disp_ctx, MY_IFACE_ID, MY_FUNC_ID,
(uint8_t *)&resp, sizeof(resp));
}
// Function callback in the FIDL registry (normally generated): the dispatcher
// calls it for every frame of this function
static void my_func_cbk(fp_dispatch_info_t *p_disp_ctx) {
fp_disp_server_handle_req(p_disp_ctx, FP_DISP_SERVER_REQ_TYPE_REGULAR,
&my_request_handler, sizeof(my_req_t));
}
static const fidl_func_t my_funcs[] = {
{ .func_id = MY_FUNC_ID, .p_on_func_cbk = &my_func_cbk }
};
static const fidl_if_t my_interface = {
.version = 1,
.gen_type = FIDL_TYPE_SERVER,
.fidl_if_id = MY_IFACE_ID,
.func_count = 1,
.p_func_list = my_funcs
};
// At start-up:
(void)fp_registry_add_fidl_if(&my_interface);Client Side:
// Step 1: Send ARM request
fp_cmd_status_t arm_status = fp_disp_client_send_req(
&disp_ctx, 0x1234, 0x5678, FP_FRAME_TYPE_REQ_ARM,
request_buffer, sizeof(request_buffer), response_buffer, NULL, 5000);
if (arm_status == FP_CMD_STATUS_ARMED) {
// Step 2: User confirmation
printf("Operation armed. Confirm execution? (y/n): ");
char confirm = getchar();
if (confirm == 'y') {
// Step 3: Send FIRE request with same data
fp_cmd_status_t fire_status = fp_disp_client_send_req(
&disp_ctx, 0x1234, 0x5678, FP_FRAME_TYPE_REQ_FIRE,
request_buffer, sizeof(request_buffer), response_buffer, NULL, 5000);
if (fire_status == FP_CMD_STATUS_NOERROR) {
printf("Operation executed successfully\n");
}
} else {
// Step 3: Send DISARM request
fp_disp_client_send_req(&disp_ctx, 0x1234, 0x5678, FP_FRAME_TYPE_REQ_DISARM,
request_buffer, sizeof(request_buffer),
response_buffer, NULL, 5000);
}
}Server Side:
#define CRITICAL_FUNC_ID ((uint32_t)0x5679)
// Runs only for a FIRE request whose data matches the armed request. The
// service answers ARM (ARMED), DISARM (DISARMED) and mismatching FIRE data
// (DATA_INTEGRITY_ERR) itself.
static void critical_operation_handler(void *p_disp_ctx, void *p_untyped_req) {
(void)p_untyped_req;
printf("Executing critical operation\n");
// Perform the actual operation
// ...
my_resp_t resp = { .result = 0U };
(void)fp_disp_server_send_resp((fp_dispatch_info_t *)p_disp_ctx, MY_IFACE_ID, CRITICAL_FUNC_ID,
(uint8_t *)&resp, sizeof(resp));
}
// ARM-and-FIRE is selected per function, when the request is dispatched.
// Register it in the interface's fidl_func_t list as shown above:
// { .func_id = CRITICAL_FUNC_ID, .p_on_func_cbk = &critical_func_cbk }
static void critical_func_cbk(fp_dispatch_info_t *p_disp_ctx) {
fp_disp_server_handle_req(p_disp_ctx, FP_DISP_SERVER_REQ_TYPE_ARM_AND_FIRE,
&critical_operation_handler, sizeof(my_req_t));
}- Session Storage: Pre-allocated pools prevent dynamic allocation
- Zero-Copy: Communication buffers passed by reference
- Minimal Overhead: Efficient session tracking and management
- Session Lookup: O(n) where n = max sessions (typically β€ 10)
- Hash Calculation: O(m) where m = message size
- Lock Contention: Minimized through context-specific locking
- Concurrent Sessions: Limited by configuration (default: 5 client + 5 server)
- Transport Independence: Multiple contexts can operate simultaneously
- Thread Safety: Full multi-threaded operation support
Cause: All sessions in use or lock timeout Solution:
- Increase
MAX_CLIENT_SESSIONS_COUNTinfp_config.h - Check for session leaks (responses not received)
- Verify lock implementation doesn't deadlock
Cause: Request data changed between ARM and FIRE Solution:
- Ensure identical data in ARM and FIRE requests
- Check for memory corruption
- Verify hash function implementation
Cause: FIDL interface not registered or ARM session expired Solution:
- Verify
fp_registry_add_fidl_if()called - Check interface ID and function ID match
- For ARM-and-FIRE: ensure FIRE sent within timeout
Cause: Network delays, processing delays, or incorrect timeout values Solution:
- Increase timeout values for slow operations
- Check transport layer connectivity
- Verify timer implementation accuracy
#define FP_ARCH_TRACE_DEBUG(format, ...) printf("[FP_DEBUG] " format "\n", ##__VA_ARGS__)
#define FP_ARCH_TRACE_INFO(format, ...) printf("[FP_INFO] " format "\n", ##__VA_ARGS__)
#define FP_ARCH_TRACE_ERROR(format, ...) printf("[FP_ERROR] " format "\n", ##__VA_ARGS__)FP_ARCH_TRACE_WARN and FP_ARCH_TRACE_FATAL must stay defined as well
(empty is fine).
// Check session metrics
fp_disp_session_metrics_t *client_metrics = &sys_ctx.client_metrics;
printf("Active sessions: %u\n", client_metrics->active_session_cnt);
printf("Timed out sessions: %u\n", client_metrics->timed_out_session_cnt);// Log message headers
fp_msg_hdr_t *hdr = (fp_msg_hdr_t *)message_buffer;
printf("iface_id: 0x%04X, func_id: 0x%08X, cmd_id: 0x%04X, status: 0x%02X\n",
hdr->iface_id, hdr->func_id, hdr->cmd_id, hdr->status);fp_cmd_status_t fp_disp_client_send_req(
fp_dispatch_info_t *p_disp_ctx,
const uint16_t iface_id,
const uint32_t func_id,
const fp_frame_type_t frame_type,
uint8_t *const p_req,
const uint32_t req_size,
uint8_t *const p_resp,
fp_generic_response_handler_t p_resp_handler,
const uint32_t timeout
);
// Same, but a synchronous response is never copied beyond resp_max_size bytes
fp_cmd_status_t fp_disp_client_send_req_bounded(
fp_dispatch_info_t *p_disp_ctx,
const uint16_t iface_id,
const uint32_t func_id,
const fp_frame_type_t frame_type,
uint8_t *const p_req,
const uint32_t req_size,
uint8_t *const p_resp,
const uint32_t resp_max_size,
fp_generic_response_handler_t p_resp_handler,
const uint32_t timeout
);
bool fp_disp_client_handle_resp(
fp_dispatch_info_t *p_disp_ctx,
const uint32_t resp_type_size
);
// Responses whose interface has no registered client
bool fp_disp_client_handle_resp_unmatched(fp_dispatch_info_t *p_disp_ctx);
void fp_disp_client_handle_bcast(
fp_dispatch_info_t *p_disp_ctx,
const uint32_t bcast_type_size,
fp_generic_broadcast_handler_t p_bcast_handler
);void fp_disp_server_handle_req(
fp_dispatch_info_t *p_disp_ctx,
const fp_disp_server_req_type_t req_type,
fp_generic_request_handler_t p_req_handler,
const uint32_t req_type_size
);
bool fp_disp_server_send_resp(
fp_dispatch_info_t *p_disp_ctx,
const uint16_t iface_id,
const uint32_t func_id,
uint8_t *const p_resp,
const uint32_t resp_size
);bool fp_registry_add_fidl_if(const fidl_if_t *const p_handler);
bool fp_registry_remove_fidl_if(const fidl_if_t *const p_handler);
const fidl_if_t *fp_registry_get_fidl_if_by_id(const uint16_t fidl_if_id,
const fidl_gen_type_t gen_type);void fp_proto_get_func_attributes(const fp_msg_hdr_t *const p_fp_hdr,
fp_proto_func_attr_t *const p_attr);
void fp_proto_set_frame_type(fp_msg_hdr_t *const p_fp_hdr,
const fp_frame_type_t frame_type);
void fp_proto_set_function_type(fp_msg_hdr_t *const p_fp_hdr,
const uint32_t func_id,
const fp_frame_type_t frame_type);
void fp_proto_set_func_id(fp_msg_hdr_t *const p_fp_hdr,
const uint32_t func_id);
bool fp_proto_is_bare_func_id_matching(const uint32_t func_id_1,
const uint32_t func_id_2);The test/ directory contains comprehensive unit tests covering:
- Protocol message handling
- Session management
- ARM-and-FIRE logic
- Error conditions
- Platform interface mocking
Recommended integration test scenarios:
- Basic Request/Response: Verify normal operation
- ARM-and-FIRE Flow: Test critical operation handling
- Timeout Scenarios: Verify timeout handling and cleanup
- Error Conditions: Test error propagation and recovery
- Concurrent Operations: Multi-threaded stress testing
- Transport Integration: Test with actual transport layers
Requirements: CMake 3.14 or newer, a C11 and C++14 compiler (GCC or Clang),
git, and gcovr for the coverage target. Catch2 v3 is used
from the system when installed, otherwise it is fetched from GitHub. The
.devcontainer.json provides a ready environment.
cmake -S test -B build/test
cmake --build build/test -j
./build/test/start-test
# Coverage report (HTML and Cobertura) in build/test/cov-report
cmake --build build/test --target gen-test-coverageRequires a Java runtime and PlantUML: a plantuml command, or plantuml.jar
in diagrams/. If neither is found, the script asks before downloading the
JAR; --download downloads the latest JAR without asking (use it in
non-interactive shells). See diagrams/README.md.
cd diagrams
./generate_svg.sh # non-interactive: ./generate_svg.sh --download
# Open svg/*.svg files in a browserContributions are welcome β see CONTRIBUTING.md for how to report issues and propose changes, and SECURITY.md for reporting vulnerabilities.
- Code Style: Follow existing C coding conventions
- Documentation: Update diagrams and README for changes
- Testing: Add tests for new functionality
- Platform Support: Maintain compatibility across platforms
When modifying the system:
- Update relevant PlantUML diagrams
- Regenerate SVG files:
cd diagrams && ./generate_svg.sh - Verify diagrams reflect actual implementation
FP is source-available under the EnduroSat Software License v1.0. See LICENSE for the full terms.
Copyright 2026 ENDUROSAT EAD
For technical support or questions about the FP service, please contact EnduroSat support.