|
Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
|
Accepted - design of record. Lands before ADR-036's train-session multiplexing, so the Train gateway service is the clean sole owner of per-train demux when that work begins.
Role C ("the Gateway") is the non-vital I/O relay between the external Train / IL / CTC simulators and the 2oo2 A/B decision channels. Today it is one binary (safeapi_gateway_c), one process per site (c-west, c-east), whose cyclic executive (gateway_c.c) calls three independent handlers in sequence:
The three handlers share no runtime state. Each owns exactly one sapi_example_relay_kind_t and one sim-facing link; the port scheme (common_config.h, site_config.h) already gives each kind its own dedicated TCP ports per peer. The only thing binding them is gateway_c.c's appmanager / cyclic-timer / config / channel-resolver / channel-down-reboot boilerplate.
The integrator wants role C built from several services rather than one app - IL, CTC and Train as separate binaries - as a precursor to the multi-train work and to a planned future inter-service communication channel between them.
safeAPIRBC2oo2GP builds:
Each main_c_<svc>.c is ~15 lines: it defines a gateway_c_service_def_t literal - { name, handler_init, handler_execute, handler_shutdown, handler_check_link_down, pre_handlers } - and calls gateway_c_main(argc, argv, &SVC). pre_handlers is gateway_c_logctl_poll for IL, NULL otherwise.
The old safeapi_gateway_c binary and the safeapi_monitor_c symlink are removed. gateway_c.c is deleted (its contents split between gateway_c_common.c and the three main_c_<svc>.c).
Project ownership follows the A/B-side split. IL and CTC are safeAPIRBC2oo2GA's domain (that project already owns src/AB/il/ab_ga_il.c and src/AB/ctc/ab_ga_ctc.c), so the IL and CTC gateway services live in GA too: safeAPIRBC2oo2GA/src/C/il/{gateway_c_il_handler,gateway_c_logctl,main_c_il}.c and safeAPIRBC2oo2GA/src/C/ctc/{gateway_c_ctc_handler,main_c_ctc}.c (one subfolder per handler, mirroring src/AB/{il,ctc}/). GA builds safeapi_gateway_c_il / _ctc; GP keeps only safeapi_gateway_c_train. gateway_c_common is transport/lifecycle - platform scope - so it stays in GP, defined before GP's add_subdirectory(safeAPIRBC2oo2GA) so GA can link it; a standalone GA build (conan) just builds the .so and skips the two executables. Because GA is add_subdirectory()'d by GP, the two GA-built binaries install into GP's own dist/<platform>/bin/ alongside the train binary (GP install(TARGETS ...) cross-dir, exactly as it already does for safeapi_rbc2oo2_ga), so the SA installer and setupLocalTestEnv.sh still see all three in one place - no change needed on either.
Each service runs per-site (safeapi_gateway_c_train WEST, ...) - the A/B relay links are per-site and this keeps ab_gp_channel.c completely unchanged (A/B still connects to a listener on the same port; that it is now a different process on the C host is invisible).
The deployment unit stays one container per site: c-west / c-east are unchanged as Docker service / container names. Inside, a shipped /app/bin/gateway_c_entrypoint.sh <SITE> launches the three service binaries and exits non-zero the moment any of them exits (wait -n, or a PID-poll fallback) - a crashed gateway service fails c-west loudly, the same "fail loud, don't limp" posture as the SA installer self-check (ADR notes in safeAPIRBC2oo2SA/CLAUDE.md). Consequences of keeping the container name:
setupLocalTestEnv.sh launches 6 gateway processes (3 per site) instead of 2, each ts_launch.py-wrapped into its own dist/local-test-env/logs/c_<svc>_<site>.log. ContainerSupportFunctions.get_container_log("c-west") in local mode merges the three c_*_west.log files by timestamp, so robot assertions that scrape c-west's log keep working unchanged. ContainerLogAttachListener._ALL_SERVICES gains c-train-west/c-il-west/c-ctc-west (+ east) for the --stream-logs live view (per-service visibility); the per-test merged .txt continues to attribute them under those names.
gateway_c_logctl.c moves into the IL service (it fans RBC_MSG_SET_LOG_LEVEL to A/B over the IL relay - its natural home). All three services still read SAPI_RBC_LOG_LEVEL at startup for their own threshold; all three still poll SAPI_RBC_LOG_LEVEL_FILE for their own level (cheap, one fopen/fgets per cycle) but only the IL service broadcasts the change to A/B. One file write still re-levels the whole site.
Out of scope here, but the separate-process split is chosen specifically so it can be added cleanly later - e.g. a local sapi_channel_service loopback or shared-NVM region letting the Train service tell the IL service which trains are live. Nothing in this ADR precludes it; it is the reason "three source files in one process" was rejected in favour of three binaries.