Safe API Framework 0.1.0 build 384e6ae
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
redundancy_multi_site.c
Go to the documentation of this file.
1
13
14#include <stdio.h>
15#include <string.h>
16#include <stdint.h>
17#include <time.h>
18
20
21/* ============================================================================
22 * CRC32 (reuse from single-site example)
23 * ========================================================================== */
24
25static uint32_t crc32(const uint8_t *data, uint32_t len)
26{
27 uint32_t crc = 0xffffffff;
28 uint32_t i;
29
30 if (data == NULL) return 0;
31
32 for (i = 0; i < len; i++) {
33 uint8_t byte = data[i];
34 crc = (crc >> 8) ^ (uint32_t)(crc ^ byte);
35 }
36
37 return crc ^ 0xffffffff;
38}
39
40/* ============================================================================
41 * Safe State Generation
42 * ========================================================================== */
43
44static train_command_t safe_command(uint32_t train_id)
45{
46 train_command_t safe = {
47 .train_id = train_id,
48 .position = 0,
49 .signal_state = 0,
50 .emergency_stop = 1,
51 .speed_limit = 0,
52 .timestamp_ms = 0,
53 .checksum = 0,
54 .sequence_number = 0
55 };
56 return safe;
57}
58
59/* ============================================================================
60 * Per-Site Voting Logic
61 * ========================================================================== */
62
63static int site_vote(site_context_t *site,
64 const train_command_t *cmd_a,
65 const train_command_t *cmd_b)
66{
67 if (site == NULL || cmd_a == NULL || cmd_b == NULL) {
68 return -1;
69 }
70
71 site->stats.total_votes++;
72
73 /* Verify checksums */
74 uint32_t checksum_a = crc32((const uint8_t *)cmd_a,
75 sizeof(*cmd_a) - sizeof(cmd_a->checksum));
76 uint32_t checksum_b = crc32((const uint8_t *)cmd_b,
77 sizeof(*cmd_b) - sizeof(cmd_b->checksum));
78
79 if (checksum_a != cmd_a->checksum) {
80 site->stats.channel_a_errors++;
81 snprintf(site->channels.service_output, sizeof(site->channels.service_output),
82 "[SITE %s] Channel A corruption (CRC fail), using safe state",
83 site->site_id == SITE_WEST ? "WEST" : "EAST");
84 site->channels.voting_result = 2;
85 site->channels.voted_command = safe_command(cmd_a->train_id);
86 return 0;
87 }
88
89 if (checksum_b != cmd_b->checksum) {
90 site->stats.channel_b_errors++;
91 snprintf(site->channels.service_output, sizeof(site->channels.service_output),
92 "[SITE %s] Channel B corruption (CRC fail), using safe state",
93 site->site_id == SITE_WEST ? "WEST" : "EAST");
94 site->channels.voting_result = 2;
95 site->channels.voted_command = safe_command(cmd_b->train_id);
96 return 0;
97 }
98
99 /* Compare commands */
100 if (cmd_a->train_id != cmd_b->train_id ||
101 cmd_a->position != cmd_b->position ||
102 cmd_a->signal_state != cmd_b->signal_state ||
103 cmd_a->speed_limit != cmd_b->speed_limit) {
104
105 /* MISMATCH */
106 site->stats.total_mismatches++;
107 site->channels.voting_result = 1;
108 site->channels.voted_command = safe_command(cmd_a->train_id);
109
110 snprintf(site->channels.service_output, sizeof(site->channels.service_output),
111 "[SITE %s] MISMATCH: A(train=%u,sig=%u,spd=%u) vs "
112 "B(train=%u,sig=%u,spd=%u) → SAFE_STATE",
113 site->site_id == SITE_WEST ? "WEST" : "EAST",
114 cmd_a->train_id, cmd_a->signal_state, cmd_a->speed_limit,
115 cmd_b->train_id, cmd_b->signal_state, cmd_b->speed_limit);
116 return 0;
117 }
118
119 /* MATCH */
120 site->stats.total_matches++;
121 site->channels.voting_result = 0;
122 site->channels.voted_command = *cmd_a;
123 site->channels.channel_a = *cmd_a;
124 site->channels.channel_b = *cmd_b;
125
126 snprintf(site->channels.service_output, sizeof(site->channels.service_output),
127 "[SITE %s] MATCH: train=%u, pos=%u, sig=%u, speed=%u km/h",
128 site->site_id == SITE_WEST ? "WEST" : "EAST",
129 cmd_a->train_id, cmd_a->position,
130 cmd_a->signal_state, cmd_a->speed_limit);
131 return 0;
132}
133
134/* ============================================================================
135 * Initialization
136 * ========================================================================== */
137
139 const multi_site_config_t *config)
140{
141 if (voter == NULL || config == NULL) {
142 return -1;
143 }
144
145 memset(voter, 0, sizeof(*voter));
146 voter->config = *config;
147
148 /* Initialize West (ONLINE) */
149 voter->west.site_id = SITE_WEST;
150 voter->west.state = SITE_STATE_ONLINE;
151 voter->west.heartbeat_timestamp_ms = (uint32_t)(time(NULL) * 1000);
152
153 /* Initialize East (STANDBY) */
154 voter->east.site_id = SITE_EAST;
155 voter->east.state = SITE_STATE_STANDBY;
156 voter->east.heartbeat_timestamp_ms = (uint32_t)(time(NULL) * 1000);
157
158 /* West is initially active */
159 voter->active_site = SITE_WEST;
160
161 fprintf(stderr,
162 "[MULTI-SITE] Initialized: %s (ONLINE) vs %s (STANDBY)\n",
163 config->west_site_name,
164 config->east_site_name);
165
166 return 0;
167}
168
169/* ============================================================================
170 * Voting Cycle
171 * ========================================================================== */
172
174 site_id_t site,
175 const train_command_t *cmd_a,
176 const train_command_t *cmd_b)
177{
178 if (voter == NULL || cmd_a == NULL || cmd_b == NULL) {
179 return -1;
180 }
181
182 site_context_t *site_ctx = (site == SITE_WEST) ? &voter->west : &voter->east;
183
184 /* Update sequence number */
185 site_ctx->stats.sequence_number++;
186
187 /* Perform 2oo2 voting */
188 return site_vote(site_ctx, cmd_a, cmd_b);
189}
190
191/* ============================================================================
192 * Heartbeat & Failover
193 * ========================================================================== */
194
196{
197 if (voter == NULL) {
198 return voter->active_site;
199 }
200
201 uint32_t now = (uint32_t)(time(NULL) * 1000);
202
203 /* Check if active site is still responding */
204 site_context_t *active_site_ctx =
205 (voter->active_site == SITE_WEST) ? &voter->west : &voter->east;
206 site_context_t *standby_site_ctx =
207 (voter->active_site == SITE_WEST) ? &voter->east : &voter->west;
208
209 uint32_t time_since_active = now - active_site_ctx->heartbeat_timestamp_ms;
210
211 if (time_since_active > voter->config.heartbeat_timeout_ms) {
212 active_site_ctx->heartbeat_miss_count++;
213
214 if (active_site_ctx->heartbeat_miss_count >=
215 voter->config.max_heartbeat_misses) {
216
217 /* FAILOVER TRIGGERED */
218 fprintf(stderr,
219 "\n[MULTI-SITE FAILOVER] %s lost heartbeat (%u ms), "
220 "switching to %s\n\n",
221 active_site_ctx->site_id == SITE_WEST ? "WEST" : "EAST",
222 time_since_active,
223 standby_site_ctx->site_id == SITE_WEST ? "WEST" : "EAST");
224
225 /* Mark old active as faulty */
226 active_site_ctx->state = SITE_STATE_FAULTY;
227
228 /* Promote standby to online */
229 standby_site_ctx->state = SITE_STATE_ONLINE;
230 voter->active_site =
231 (voter->active_site == SITE_WEST) ? SITE_EAST : SITE_WEST;
232
233 /* Reset heartbeat timer */
234 standby_site_ctx->heartbeat_timestamp_ms = now;
235 standby_site_ctx->heartbeat_miss_count = 0;
236
237 return voter->active_site;
238 }
239 } else {
240 /* Heartbeat restored, reset counter */
241 active_site_ctx->heartbeat_miss_count = 0;
242 }
243
244 /* Update active site heartbeat */
245 active_site_ctx->heartbeat_timestamp_ms = now;
246
247 return voter->active_site;
248}
249
250/* ============================================================================
251 * Manual Failover
252 * ========================================================================== */
253
255 site_id_t new_active)
256{
257 if (voter == NULL) {
258 return -1;
259 }
260
261 if (new_active == voter->active_site) {
262 return 0; /* Already active */
263 }
264
265 site_context_t *old_active =
266 (voter->active_site == SITE_WEST) ? &voter->west : &voter->east;
267 site_context_t *new_active_ctx = (new_active == SITE_WEST) ? &voter->west : &voter->east;
268
269 fprintf(stderr,
270 "[MULTI-SITE FAILOVER] Manual request: switching from %s to %s\n",
271 old_active->site_id == SITE_WEST ? "WEST" : "EAST",
272 new_active == SITE_WEST ? "WEST" : "EAST");
273
274 old_active->state = SITE_STATE_STANDBY;
275 new_active_ctx->state = SITE_STATE_ONLINE;
276 voter->active_site = new_active;
277
278 return 0;
279}
280
281/* ============================================================================
282 * Query Active Command
283 * ========================================================================== */
284
286 train_command_t *cmd_out)
287{
288 if (voter == NULL || cmd_out == NULL) {
289 return -1;
290 }
291
292 site_context_t *active = (voter->active_site == SITE_WEST) ?
293 &voter->west : &voter->east;
294
295 *cmd_out = active->channels.voted_command;
296 return 0;
297}
298
299/* ============================================================================
300 * Query Service Output (C Channel)
301 * ========================================================================== */
302
304 char *output_buf,
305 uint32_t buf_size)
306{
307 if (voter == NULL || output_buf == NULL || buf_size == 0) {
308 return -1;
309 }
310
311 site_context_t *active = (voter->active_site == SITE_WEST) ?
312 &voter->west : &voter->east;
313
314 strncpy(output_buf, active->channels.service_output, buf_size - 1);
315 output_buf[buf_size - 1] = '\0';
316
317 return (int)strlen(output_buf);
318}
319
320/* ============================================================================
321 * Statistics
322 * ========================================================================== */
323
325{
326 if (voter == NULL) {
327 return;
328 }
329
330 printf("\n");
331 printf("═══════════════════════════════════════════════════════════════\n");
332 printf("Multi-Site Voting Statistics\n");
333 printf("═══════════════════════════════════════════════════════════════\n");
334
335 printf("\nWEST Site (%s):\n", voter->west.state == SITE_STATE_ONLINE ? "ONLINE" :
336 voter->west.state == SITE_STATE_STANDBY ? "STANDBY" : "FAULTY");
337 printf(" Total votes: %u\n", voter->west.stats.total_votes);
338 printf(" Matches: %u (%.1f%%)\n", voter->west.stats.total_matches,
339 voter->west.stats.total_votes > 0 ?
340 (100.0 * voter->west.stats.total_matches / voter->west.stats.total_votes) : 0.0);
341 printf(" Mismatches: %u\n", voter->west.stats.total_mismatches);
342 printf(" Ch-A errors: %u\n", voter->west.stats.channel_a_errors);
343 printf(" Ch-B errors: %u\n", voter->west.stats.channel_b_errors);
344
345 printf("\nEAST Site (%s):\n", voter->east.state == SITE_STATE_ONLINE ? "ONLINE" :
346 voter->east.state == SITE_STATE_STANDBY ? "STANDBY" : "FAULTY");
347 printf(" Total votes: %u\n", voter->east.stats.total_votes);
348 printf(" Matches: %u (%.1f%%)\n", voter->east.stats.total_matches,
349 voter->east.stats.total_votes > 0 ?
350 (100.0 * voter->east.stats.total_matches / voter->east.stats.total_votes) : 0.0);
351 printf(" Mismatches: %u\n", voter->east.stats.total_mismatches);
352 printf(" Ch-A errors: %u\n", voter->east.stats.channel_a_errors);
353 printf(" Ch-B errors: %u\n", voter->east.stats.channel_b_errors);
354
355 printf("\nActive Site: %s\n", voter->active_site == SITE_WEST ? "WEST" : "EAST");
356 printf("═══════════════════════════════════════════════════════════════\n");
357 printf("\n");
358}
359
360/* ============================================================================
361 * Shutdown
362 * ========================================================================== */
363
365{
366 if (voter == NULL) {
367 return -1;
368 }
369
370 fprintf(stderr, "[MULTI-SITE] Shutting down voter\n");
371 voter->west.state = SITE_STATE_FAULTY;
372 voter->east.state = SITE_STATE_FAULTY;
373
374 return 0;
375}
int multi_site_get_service_output(multi_site_voter_t *voter, char *output_buf, uint32_t buf_size)
Get service output from active site (for printf, logging).
void multi_site_print_stats(const multi_site_voter_t *voter)
Print multi-site statistics for diagnostics.
int multi_site_voter_shutdown(multi_site_voter_t *voter)
Shutdown multi-site voter.
int multi_site_voter_init(multi_site_voter_t *voter, const multi_site_config_t *config)
Initialize multi-site voter with West online, East standby.
int multi_site_get_active_command(multi_site_voter_t *voter, train_command_t *cmd_out)
Get voted command from currently active site.
int multi_site_vote_cycle(multi_site_voter_t *voter, site_id_t site, const train_command_t *cmd_a, const train_command_t *cmd_b)
Process voting cycle on one site.
site_id_t multi_site_heartbeat(multi_site_voter_t *voter)
Check heartbeat between sites and trigger failover if needed.
int multi_site_failover_request(multi_site_voter_t *voter, site_id_t new_active)
Trigger manual failover (for maintenance).
Multi-Site 2oo2 Redundancy with Active/Standby Failover.