RTE RBC 2oo2 Reference Application 0.1.0 build 0006685
SIL 4 2oo2 Dual-Site Fault-Tolerant ERTMS Radio Block Centre Reference Architecture
Loading...
Searching...
No Matches
gateway_c_train_handler.c
Go to the documentation of this file.
1
8
9#include <stdio.h>
10#include "rte/oal/memory/rte_mem_util.h"
11#include "rte/oal/log/rte_log.h"
14#include "gateway_c_common.h"
16
18
19static const char *peer_name(gateway_c_peer_t peer)
20{
21 return (peer == GATEWAY_C_PEER_A) ? "A" : "B";
22}
23
25{
26 rte_status_t status;
27
28 g_train.last_known_tag = "SUPPRESSED";
37
38 RTE_EXAMPLE_LOG("[GW %s] GP: opening channel 'c-relay-train-a' to RTE\n", site_config_name(ctx->site));
39 status = rte_channel_service_setup(&g_train.relay_channel[GATEWAY_C_PEER_A], "c-relay-train-a");
40 if (status != RTE_STATUS_OK)
41 {
42 RTE_EXAMPLE_LOG("[GW %s] TrainHandler: relay A channel setup failed (%s)\n",
43 site_config_name(ctx->site), rte_status_to_string(status));
44 return status;
45 }
46 RTE_EXAMPLE_LOG("[GW %s] GP: opening channel 'c-relay-train-b' to RTE\n", site_config_name(ctx->site));
47 status = rte_channel_service_setup(&g_train.relay_channel[GATEWAY_C_PEER_B], "c-relay-train-b");
48 if (status != RTE_STATUS_OK)
49 {
50 RTE_EXAMPLE_LOG("[GW %s] TrainHandler: relay B channel setup failed (%s)\n",
51 site_config_name(ctx->site), rte_status_to_string(status));
52 return status;
53 }
54 /* REQ-RBC-009: c-sim-train is listen-role (etc/config/c_<site>.json), so this setup only binds the local port
55 * and never waits for the Train sim, which may start later. The peer is the most recent sender, so a sim that
56 * restarts on a new source port is reached again; a send before the first datagram returns NOT_CONNECTED,
57 * which send_to_sim() treats like OK. */
58 RTE_EXAMPLE_LOG("[GW %s] GP: opening channel 'c-sim-train' to RTE\n", site_config_name(ctx->site));
59 status = rte_channel_service_setup(&g_train.sim_channel, "c-sim-train");
60 if (status != RTE_STATUS_OK)
61 {
62 RTE_EXAMPLE_LOG("[GW %s] TrainHandler: sim channel setup failed (%s)\n",
63 site_config_name(ctx->site), rte_status_to_string(status));
64 return status;
65 }
66 return RTE_STATUS_OK;
67}
68
69/* @p len is caller-supplied (not hardcoded) since this relay now carries
70 * two different frame sizes on the same link: RBC_ENVELOPE_WIRE_SIZE
71 * (96, the flat scheme's own kinds, keepalives included) and
72 * RBC_TRAIN_ENVELOPE_WIRE_SIZE (512, RBC_MSG_ERTMS_ENVELOPE) - a caller
73 * sending the smaller shape must never have this function pad it out to
74 * the bigger one from whatever happens to follow its buffer. */
76 const uint8_t *bytes, size_t len)
77{
78 rte_status_t status = rte_channel_service_send(&g_train.relay_channel[peer], bytes, len, 0U);
79 /* NOT_CONNECTED (REQ-CHANSVC-001): this listen-role relay has not had a datagram from A/B yet (still starting),
80 * so nothing was sent. That is not a failed send: handled exactly as OK, as before the status existed (the
81 * channel service then returned OK and dropped the frame), so no send-failure window and no log. */
82 if ((status == RTE_STATUS_OK) || (status == RTE_STATUS_NOT_CONNECTED))
83 {
84 /* A send that goes out again ends any send-failure window recorded below. Receive-side
85 * silence alone cannot detect a dead link on UDP (a read just times out), so this and the
86 * failure case below are what feed gateway_c_train_handler_check_link_down(). */
87 g_train.send_down_since_ms[peer] = 0U;
88 }
89 else
90 {
91 /* TIMEOUT counts too: the POSIX channel backend maps EHOSTUNREACH/ECONNREFUSED/ENOTCONN (what a partitioned
92 * peer produces) to TIMEOUT, so ignoring it left a partitioned Gateway running for good (Docker, C West
93 * partition test). Any successful send resets the window, so a busy queue cannot trip it. */
94 if (g_train.send_down_since_ms[peer] == 0U)
95 {
96 (void)rte_timer_now(&g_train.send_down_since_ms[peer]);
97 }
98 if (status == RTE_STATUS_TIMEOUT)
99 {
100 return;
101 }
102 RTE_EXAMPLE_LOG("[GW %s] TrainHandler: forward to %s failed (%s)\n", site_config_name(ctx->site),
103 peer_name(peer), rte_status_to_string(status));
104 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(ctx->site), 0U, "GW", peer_name(peer),
105 "ENVELOPE", rte_status_to_string(status), NULL);
106 }
107}
108
109static void send_to_sim(gateway_c_context_t *ctx, const uint8_t *bytes, size_t len)
110{
111 rte_status_t status = rte_channel_service_send(&g_train.sim_channel, bytes, len, 0U);
112 /* NOT_CONNECTED: no Train sim has sent on this listen-role link yet; silent, as OK was before REQ-CHANSVC-001. */
113 if ((status != RTE_STATUS_OK) && (status != RTE_STATUS_TIMEOUT) && (status != RTE_STATUS_NOT_CONNECTED))
114 {
115 RTE_EXAMPLE_LOG("[GW %s] TrainHandler: forward to TrainSim failed (%s)\n",
116 site_config_name(ctx->site), rte_status_to_string(status));
117 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(ctx->site), 0U, "GW", "Train", "ENVELOPE",
118 rte_status_to_string(status), NULL);
119 }
120}
121
123{
124 /* Sized for the widest frame this relay carries now
125 * (RBC_MSG_ERTMS_ENVELOPE, RBC_TRAIN_ENVELOPE_WIRE_SIZE) - the flat
126 * scheme's own smaller kinds (still RBC_ENVELOPE_WIRE_SIZE), and any
127 * future SafeComm SaF/SAI frame smaller than that, all ride the same
128 * buffer. Read via rte_channel_service_read_ex(): the underlying
129 * transport is a datagram (rte_netlink UDP) that already preserves
130 * message boundaries, and _ex reports the true received length instead
131 * of always claiming the full buffer - this function forwards exactly
132 * that many bytes, never the whole buffer padded with stale/zeroed
133 * tail. Zeroed before every read below (not just once here) so the
134 * unused tail, when a frame IS shorter, is always deterministic zero,
135 * never a previous frame's leftover bytes, should anything downstream
136 * ever look past actual_len. */
137 uint8_t bytes[RBC_TRAIN_ENVELOPE_WIRE_SIZE];
138 size_t actual_len;
139 char sim_status[RBC_UTIL_PAYLOAD_MAX + 1];
140 rte_status_t status;
141 bool forwarded_this_cycle = false;
142 (void)cycle;
143
144 /* TrainSim -> Gateway -> A and B (verbatim broadcast, no train_id
145 * inspection - A/B route each envelope by its own train_id). */
146 rte_mem_set(bytes, 0, sizeof(bytes));
147 while (rte_channel_service_read_ex(&g_train.sim_channel, bytes, sizeof(bytes), 0U, &actual_len) ==
148 RTE_STATUS_OK)
149 {
150 /* ADR-038: the Train sim's own-perspective status frame - republish
151 * it to the C status broker, never forward it to A/B (they never
152 * see this kind). Not counted as relay traffic (the idle-cycle
153 * keepalive below still fires if this was the only frame). */
154 if (rbc_envelope_is_sim_status(bytes, sim_status, (uint16_t)sizeof(sim_status)))
155 {
157 rte_mem_set(bytes, 0, sizeof(bytes));
158 continue;
159 }
160 send_to_relay(ctx, GATEWAY_C_PEER_A, bytes, actual_len);
161 send_to_relay(ctx, GATEWAY_C_PEER_B, bytes, actual_len);
162 forwarded_this_cycle = true;
163 rte_mem_set(bytes, 0, sizeof(bytes));
164 }
165
166 /* ADR-036: with ONE multiplexed link, an idle Train sim (M136 every
167 * simPeriodSeconds, ~2s) leaves the C->A/B relay direction silent for
168 * longer than RTE_EXAMPLE_LINK_STALE_TIMEOUT_COUNT *
169 * _LINK_TIMEOUT_MS (~1.2s) - A/B's relay-RX staleness detector then
170 * closes the link for reconnect, cross-compare skips, and the A<->B
171 * checkpoint rendezvous times out -> reboot. Keepalive the relay
172 * every idle cycle, exactly as A/B already do C-ward for IL/CTC
173 * (post_execute.c / rbc_wire_types.h RELAY_KEEPALIVE). A/B recognise
174 * and drop it before any train decode (pre_execute.c). */
175 if (!forwarded_this_cycle)
176 {
177 uint8_t keepalive[RBC_ENVELOPE_WIRE_SIZE];
178
180 send_to_relay(ctx, GATEWAY_C_PEER_A, keepalive, sizeof(keepalive));
181 send_to_relay(ctx, GATEWAY_C_PEER_B, keepalive, sizeof(keepalive));
182 }
183
184 /* A/B -> Gateway -> TrainSim.
185 * RCA/OCORA PI-API: Gateway acts as external Service Unit / Adapter.
186 * 2oo2 arbitration is not needed here because channels A and B already
187 * compared data before emission. Gateway forwards whichever envelope arrives
188 * first and deduplicates identical frames arriving from the peer replica. */
189 {
190 uint32_t a_count = 0U;
191 uint32_t b_count = 0U;
192
193 rte_mem_set(bytes, 0, sizeof(bytes));
194 while ((status = rte_channel_service_read_ex(&g_train.relay_channel[GATEWAY_C_PEER_A],
195 bytes, sizeof(bytes), 0U, &actual_len)) == RTE_STATUS_OK)
196 {
197 if (!gateway_c_dedup_check_and_record(&g_train.dedup, bytes, actual_len))
198 {
199 send_to_sim(ctx, bytes, actual_len);
200 }
201 a_count++;
202 rte_mem_set(bytes, 0, sizeof(bytes));
203 }
204 if ((status != RTE_STATUS_OK) && (status != RTE_STATUS_TIMEOUT))
205 {
207 {
208 (void)rte_timer_now(&g_train.relay_down_since_ms[GATEWAY_C_PEER_A]);
209 }
210 }
211 else if (a_count > 0U)
212 {
216 }
217 else
218 {
220 }
221 g_train.a_seen_this_cycle = a_count;
222
223 rte_mem_set(bytes, 0, sizeof(bytes));
224 while ((status = rte_channel_service_read_ex(&g_train.relay_channel[GATEWAY_C_PEER_B],
225 bytes, sizeof(bytes), 0U, &actual_len)) == RTE_STATUS_OK)
226 {
227 if (!gateway_c_dedup_check_and_record(&g_train.dedup, bytes, actual_len))
228 {
229 send_to_sim(ctx, bytes, actual_len);
230 }
231 b_count++;
232 rte_mem_set(bytes, 0, sizeof(bytes));
233 }
234 if ((status != RTE_STATUS_OK) && (status != RTE_STATUS_TIMEOUT))
235 {
237 {
238 (void)rte_timer_now(&g_train.relay_down_since_ms[GATEWAY_C_PEER_B]);
239 }
240 }
241 else if (b_count > 0U)
242 {
244 }
245
246 g_train.last_known_tag = ((a_count > 0U) || (b_count > 0U)) ? "ACTIVE" : "SUPPRESSED";
247 }
248}
249
251{
252 (void)rte_channel_service_close(&g_train.sim_channel);
253 (void)rte_channel_service_close(&g_train.relay_channel[GATEWAY_C_PEER_A]);
254 (void)rte_channel_service_close(&g_train.relay_channel[GATEWAY_C_PEER_B]);
255}
256
257bool gateway_c_train_handler_check_link_down(rte_timestamp_ms_t threshold_ms, rte_timestamp_ms_t now_ms,
258 char *out_which, size_t which_size)
259{
260 gateway_c_peer_t peer;
261
262 for (peer = GATEWAY_C_PEER_A; peer <= GATEWAY_C_PEER_B; peer = (gateway_c_peer_t)((int)peer + 1))
263 {
264 rte_timestamp_ms_t down_since = g_train.relay_down_since_ms[peer];
265 rte_timestamp_ms_t send_down_since = g_train.send_down_since_ms[peer];
266
267 if (((down_since != 0U) && ((now_ms - down_since) >= threshold_ms)) ||
268 ((send_down_since != 0U) && ((now_ms - send_down_since) >= threshold_ms)))
269 {
270 (void)snprintf(out_which, which_size, "Train relay link to %s", peer_name(peer));
271 return true;
272 }
273 }
274 return false;
275}
#define RTE_EXAMPLE_LOG(...)
#define RBC_ENVELOPE_WIRE_SIZE
#define RBC_TRAIN_ENVELOPE_WIRE_SIZE
void gateway_c_dedup_init(gateway_c_dedup_t *dedup)
Initialize a first-arrival deduplication tracker.
bool gateway_c_dedup_check_and_record(gateway_c_dedup_t *dedup, const uint8_t *bytes, size_t size)
Check if bytes was already forwarded within the expiry window. If not seen, records bytes with curren...
Shared skeleton for the three Role C gateway service binaries (rte_gateway_c_train / _il / _ctc - ADR...
void gateway_c_selfstatus_publish(uint8_t src_id, const char *payload)
Republish a foreign unit's status to the C broker as a PUBLISH(STATUS) with source id src_id (rbc_uti...
ADR-038: a role-C gateway service's own periodic status - a self log line plus a PUBLISH(STATUS) rbc_...
void gateway_c_train_handler_shutdown(void)
static void send_to_relay(gateway_c_context_t *ctx, gateway_c_peer_t peer, const uint8_t *bytes, size_t len)
static const char * peer_name(gateway_c_peer_t peer)
static gateway_c_train_handler_state_t g_train
bool gateway_c_train_handler_check_link_down(rte_timestamp_ms_t threshold_ms, rte_timestamp_ms_t now_ms, char *out_which, size_t which_size)
rte_status_t gateway_c_train_handler_init(gateway_c_context_t *ctx)
void gateway_c_train_handler_execute(gateway_c_context_t *ctx, uint32_t cycle)
static void send_to_sim(gateway_c_context_t *ctx, const uint8_t *bytes, size_t len)
TrainHandler - Role C Gateway's TCP/IP server for the ONE multiplexed Train sim connection and the ON...
gateway_c_peer_t
@ GATEWAY_C_PEER_A
@ GATEWAY_C_PEER_B
ADR-038: the wire frame for the A/B<->C util side channel (RTE_EXAMPLE_RELAY_KIND_UTIL) and the C-sid...
#define RBC_UTIL_PAYLOAD_MAX
@ RBC_UTIL_SRC_SIM_TRAIN
static bool rbc_envelope_is_sim_status(const uint8_t in[RBC_ENVELOPE_WIRE_SIZE], char *out_payload, uint16_t out_size)
true if in is a sim-status frame; when it is and out_payload is non-NULL, copies the ASCII payload th...
static void rbc_envelope_write_keepalive(uint8_t out[RBC_ENVELOPE_WIRE_SIZE])
Fills out with a keepalive frame (RBC_MSG_RELAY_KEEPALIVE in byte 0, every other byte zero) - see tha...
const char * site_config_name(rte_example_site_t site)
Definition site_config.c:52
One gateway_c_context_t instance's state.
rte_example_site_t site
rte_channel_service_t relay_channel[GATEWAY_C_PEER_COUNT]
rte_timestamp_ms_t relay_down_since_ms[GATEWAY_C_PEER_COUNT]
rte_timestamp_ms_t send_down_since_ms[GATEWAY_C_PEER_COUNT]