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
rte_voter.c
Go to the documentation of this file.
1
9#include <string.h>
10
13#include "rte/oal/log/rte_log.h"
15
16/* Takes channel_count as an explicit parameter (not read from voter->channel_count directly) so
17 * rte_voter_vote_buffers() below can reuse this same validation against a caller-supplied
18 * buffer count instead of registered-channel count - the same "ignores any registered channels"
19 * posture rte_cross_comparator_execute_buffers() already established for its own module. The
20 * one existing caller (rte_voter_receive()) passes voter->channel_count, unchanged behavior. */
21static bool voter_channel_count_matches_strategy(const rte_voter_t *voter, uint32_t channel_count)
22{
23 bool ok;
24
25 switch (voter->config.voting_strategy)
26 {
27 case RTE_VOTING_2OO2:
28 ok = (channel_count == 2U);
29 break;
30 case RTE_VOTING_2OO3:
31 ok = (channel_count == 3U);
32 break;
33 case RTE_VOTING_NMR:
34 ok = (channel_count >= 1U) &&
35 (voter->config.quorum_size >= 1U) &&
36 (voter->config.quorum_size <= channel_count);
37 break;
38 default:
39 ok = false;
40 break;
41 }
42 return ok;
43}
44
45static uint32_t voter_required_quorum(const rte_voter_t *voter)
46{
47 uint32_t quorum;
48
49 switch (voter->config.voting_strategy)
50 {
51 case RTE_VOTING_2OO2:
52 quorum = 2U;
53 break;
54 case RTE_VOTING_2OO3:
55 quorum = 2U;
56 break;
57 case RTE_VOTING_NMR:
58 quorum = voter->config.quorum_size;
59 break;
60 default:
61 /* Defensive: rte_voter_init() already rejects any other
62 * value, so a live voter's strategy is always one of the
63 * three above. */
64 quorum = 0xFFFFFFFFU;
65 break;
66 }
67 return quorum;
68}
69
70static bool voter_data_equal(const rte_voter_t *voter, const void *a, const void *b, size_t size)
71{
72 bool equal;
73
74 if (voter->config.compare != NULL)
75 {
76 equal = voter->config.compare(a, b, size, voter->config.compare_context);
77 }
78 else
79 {
80 equal = (memcmp(a, b, size) == 0);
81 }
82 return equal;
83}
84
85static uint32_t voter_count_healthy(const rte_voter_t *voter)
86{
87 uint32_t i;
88 uint32_t healthy = 0U;
90
91 for (i = 0U; i < voter->channel_count; i++)
92 {
93 (void)rte_channel_get_health(voter->channels[i], &h);
94 if (h.is_healthy)
95 {
96 healthy++;
97 }
98 }
99 return healthy;
100}
101
102/* rte_voter_receive()'s own receive phase, extracted: polls every healthy channel once, filling
103 * buffers[i] (valid only where responded[i] is subsequently true - unhealthy/non-responding
104 * channels' buffer slots are left whatever they were) and counting how many channels actually
105 * returned data. responded[] must already be all-false on entry (the caller zeroes it); this
106 * function only ever sets an entry true, never clears one. No logic changed from the loop this
107 * replaces - a pure extraction. */
108static uint32_t voter_receive_from_healthy_channels(rte_voter_t *voter, size_t data_size,
109 uint8_t buffers[][RTE_VOTER_MAX_MESSAGE_SIZE],
110 bool responded[], bool *out_saw_timeout)
111{
112 uint32_t successful = 0U;
113 uint32_t i;
114
115 *out_saw_timeout = false;
116 for (i = 0U; i < voter->channel_count; i++)
117 {
119 rte_status_t st;
120
121 (void)rte_channel_get_health(voter->channels[i], &h);
122 if (!h.is_healthy)
123 {
124 continue;
125 }
126
127 st = rte_channel_receive(voter->channels[i], buffers[i], data_size,
128 voter->config.channel_timeout_ms);
129 if (st == RTE_STATUS_OK)
130 {
131 responded[i] = true;
132 successful++;
133 }
134 else if (st == RTE_STATUS_TIMEOUT)
135 {
136 *out_saw_timeout = true;
137 }
138 else
139 {
140 /* Any other failure just doesn't count toward successful;
141 * saw_timeout stays as-is. */
142 }
143 }
144 return successful;
145}
146
147/* rte_voter_receive()'s own grouping phase, extracted: groups every channel whose response
148 * (buffers[i], for every i where responded[i] is true) mutually agrees with another's via
149 * voter_data_equal(), by the same plain O(n^2) pairwise scan the code being replaced used -
150 * group_id[i] names which group channel i landed in, group_count[g] is that group's current
151 * size, *out_num_groups is the total distinct-group count found. group_count[] must already be
152 * all-zero on entry (the caller zeroes it, same as before this extraction).
153 *
154 * channel_count is an explicit parameter (not read from voter->channel_count) for the same
155 * reason voter_channel_count_matches_strategy() above takes one: rte_voter_vote_buffers()
156 * reuses this same grouping logic against a caller-supplied buffer count, which may differ from
157 * (or with zero registered channels, have nothing to do with) voter->channel_count. The one
158 * existing caller (rte_voter_receive()) passes voter->channel_count, unchanged behavior. */
159static void voter_group_matching_responses(const rte_voter_t *voter, uint32_t channel_count,
160 const uint8_t buffers[][RTE_VOTER_MAX_MESSAGE_SIZE],
161 const bool responded[], size_t data_size,
162 uint32_t group_id[], uint32_t group_count[],
163 uint32_t *out_num_groups)
164{
165 uint32_t num_groups = 0U;
166 uint32_t i;
167 uint32_t j;
168
169 for (i = 0U; i < channel_count; i++)
170 {
171 bool placed = false;
172
173 if (!responded[i])
174 {
175 continue;
176 }
177 for (j = 0U; j < i; j++)
178 {
179 if (!responded[j])
180 {
181 continue;
182 }
183 if (voter_data_equal(voter, buffers[i], buffers[j], data_size))
184 {
185 group_id[i] = group_id[j];
186 group_count[group_id[i]]++;
187 placed = true;
188 break;
189 }
190 }
191 if (!placed)
192 {
193 group_id[i] = num_groups;
194 group_count[num_groups] = 1U;
195 num_groups++;
196 }
197 }
198 *out_num_groups = num_groups;
199}
200
202{
203 rte_status_t lifecycle_status;
204
205 if ((storage == NULL) || (config == NULL))
206 {
208 }
209 /* REQ-LIFECYCLE-001 (ADR-026): a voter is a setup-only resource - refuse once the
210 * application's setup phase has been locked. */
211 lifecycle_status = rte_lifecycle_check_setup_allowed();
212 if (lifecycle_status != RTE_STATUS_OK)
213 {
214 return lifecycle_status;
215 }
216
217 switch (config->voting_strategy)
218 {
219 case RTE_VOTING_2OO2:
220 case RTE_VOTING_2OO3:
221 break;
222 case RTE_VOTING_NMR:
223 if (config->quorum_size == 0U)
224 {
226 }
227 break;
228 default:
230 }
231
232 storage->config = *config;
233 storage->channel_count = 0U;
234 storage->total_disagreements = 0U;
235 storage->initialized = true;
236
237 return RTE_STATUS_OK;
238}
239
241{
242 rte_status_t lifecycle_status;
243
244 if ((voter == NULL) || (channel == NULL))
245 {
247 }
248 if (!voter->initialized)
249 {
251 }
252 /* REQ-LIFECYCLE-001 (ADR-026): registering a channel into a voter is setup-only - refuse
253 * once the application's setup phase has been locked. */
254 lifecycle_status = rte_lifecycle_check_setup_allowed();
255 if (lifecycle_status != RTE_STATUS_OK)
256 {
257 return lifecycle_status;
258 }
259 if (voter->channel_count >= RTE_VOTER_MAX_CHANNELS)
260 {
262 }
263
264 voter->channels[voter->channel_count] = channel;
265 voter->channel_count++;
266 return RTE_STATUS_OK;
267}
268
269rte_status_t rte_voter_send(rte_voter_t *voter, const void *data, size_t data_size)
270{
271 uint32_t i;
272 uint32_t healthy_count = 0U;
273 bool any_failed = false;
274 bool any_timeout = false;
276
277 if ((voter == NULL) || (data == NULL) || (data_size == 0U))
278 {
280 }
281 if (!voter->initialized)
282 {
284 }
285 if (data_size > RTE_VOTER_MAX_MESSAGE_SIZE)
286 {
288 }
289 if (!voter_channel_count_matches_strategy(voter, voter->channel_count))
290 {
292 }
293
294 for (i = 0U; i < voter->channel_count; i++)
295 {
296 rte_status_t st;
297
298 (void)rte_channel_get_health(voter->channels[i], &h);
299 if (!h.is_healthy)
300 {
301 continue;
302 }
303 healthy_count++;
304
305 st = rte_channel_send(voter->channels[i], data, data_size);
306 if (st != RTE_STATUS_OK)
307 {
308 any_failed = true;
309 if (st == RTE_STATUS_TIMEOUT)
310 {
311 any_timeout = true;
312 }
313 }
314 }
315
316 if (healthy_count == 0U)
317 {
319 }
320 if (any_timeout)
321 {
322 return RTE_STATUS_TIMEOUT;
323 }
324 if (any_failed)
325 {
327 }
328 return RTE_STATUS_OK;
329}
330
331rte_status_t rte_voter_receive(rte_voter_t *voter, void *data, size_t data_size,
332 rte_voting_result_t *result, size_t *bytes_received)
333{
335 bool responded[RTE_VOTER_MAX_CHANNELS];
336 uint32_t group_id[RTE_VOTER_MAX_CHANNELS];
337 uint32_t group_count[RTE_VOTER_MAX_CHANNELS];
338 uint32_t num_groups = 0U;
339 uint32_t successful = 0U;
340 uint32_t required_quorum;
341 uint32_t best_group = 0U;
342 uint32_t best_count = 0U;
343 bool saw_timeout = false;
344 rte_voting_result_t local_result;
345 uint32_t i;
346
347 if ((voter == NULL) || (data == NULL) || (data_size == 0U))
348 {
350 }
351 if (!voter->initialized)
352 {
354 }
355 if (data_size > RTE_VOTER_MAX_MESSAGE_SIZE)
356 {
358 }
359 if (!voter_channel_count_matches_strategy(voter, voter->channel_count))
360 {
362 }
363
364 required_quorum = voter_required_quorum(voter);
365
366 for (i = 0U; i < voter->channel_count; i++)
367 {
368 responded[i] = false;
369 group_count[i] = 0U;
370 }
371
372 if (voter_count_healthy(voter) < required_quorum)
373 {
374 local_result = RTE_VOTING_INSUFFICIENT_QUORUM;
375 }
376 else
377 {
378 successful = voter_receive_from_healthy_channels(voter, data_size, buffers, responded, &saw_timeout);
379
380 if (successful == 0U)
381 {
382 local_result = saw_timeout ? RTE_VOTING_TIMEOUT : RTE_VOTING_INSUFFICIENT_QUORUM;
383 }
384 else if (successful < required_quorum)
385 {
386 local_result = RTE_VOTING_INSUFFICIENT_QUORUM;
387 }
388 else
389 {
390 /* Explicit cast: T(*)[N] -> const T(*)[N] is not a standard implicit conversion
391 * before C2X (unlike a single-level T* -> const T*) - flagged by -Wpedantic on
392 * gcc (not clang), found via a real Linux/glibc x86_64 build (root TODO.md's
393 * "Verify an x86_64 gcc build" item). Value-safe: only adds a qualifier, never
394 * removes one. */
395 voter_group_matching_responses(
396 voter, voter->channel_count, (const uint8_t(*)[RTE_VOTER_MAX_MESSAGE_SIZE])buffers, responded,
397 data_size, group_id, group_count, &num_groups);
398
399 for (i = 0U; i < num_groups; i++)
400 {
401 if (group_count[i] > best_count)
402 {
403 best_count = group_count[i];
404 best_group = i;
405 }
406 }
407
408 if (best_count >= required_quorum)
409 {
410 for (i = 0U; i < voter->channel_count; i++)
411 {
412 if (responded[i] && (group_id[i] == best_group))
413 {
414 (void)memcpy(data, buffers[i], data_size);
415 break;
416 }
417 }
418 local_result = RTE_VOTING_AGREED;
419 }
420 else
421 {
422 local_result = RTE_VOTING_DISAGREED;
423 }
424 }
425 }
426
427 if (result != NULL)
428 {
429 *result = local_result;
430 }
431
432 if (local_result == RTE_VOTING_AGREED)
433 {
434 if (bytes_received != NULL)
435 {
436 *bytes_received = data_size;
437 }
438 return RTE_STATUS_OK;
439 }
440
441 if (bytes_received != NULL)
442 {
443 *bytes_received = 0U;
444 }
445
446 if (local_result == RTE_VOTING_DISAGREED)
447 {
448 voter->total_disagreements++;
449 if (voter->config.log_disagreements)
450 {
451 rte_log_write(RTE_LOG_LEVEL_ERROR, "voter", "channels disagreed");
452 }
453 /* Application reacts BEFORE the Platform enters safe state below -
454 * see this module's own RCA/OCORA PI-API compatibility note
455 * (rte_voter.h) for why the Platform now unconditionally owns
456 * this transition. */
457 if (voter->config.on_disagreement != NULL)
458 {
459 voter->config.on_disagreement(voter->config.disagreement_context, local_result);
460 }
461 RTE_SAFESTATE(voter->config.safestate_level, voter->config.safestate_reason);
462 }
463 else if (voter->config.on_disagreement != NULL)
464 {
465 /* Non-DISAGREED non-AGREED results (TIMEOUT/INSUFFICIENT_QUORUM)
466 * still get the callback, same as before this change, but do
467 * NOT trigger a safestate transition here - only a genuine
468 * DISAGREE does. */
469 voter->config.on_disagreement(voter->config.disagreement_context, local_result);
470 }
471 else
472 {
473 /* Nothing further to do for a non-DISAGREED result with no
474 * callback registered. */
475 }
476
478}
479
480rte_status_t rte_voter_vote_buffers(rte_voter_t *voter, const void *const *buffers, uint32_t buffer_count,
481 size_t data_size, rte_voting_result_t *result, void *out_data,
482 size_t *out_size)
483{
485 bool responded[RTE_VOTER_MAX_CHANNELS];
486 uint32_t group_id[RTE_VOTER_MAX_CHANNELS];
487 uint32_t group_count[RTE_VOTER_MAX_CHANNELS];
488 uint32_t num_groups = 0U;
489 uint32_t required_quorum;
490 uint32_t best_group = 0U;
491 uint32_t best_count = 0U;
492 rte_voting_result_t local_result;
493 uint32_t i;
494
495 if ((voter == NULL) || (buffers == NULL) || (data_size == 0U))
496 {
498 }
499 if (!voter->initialized)
500 {
502 }
503 if (data_size > RTE_VOTER_MAX_MESSAGE_SIZE)
504 {
506 }
507 if (buffer_count > (uint32_t)RTE_VOTER_MAX_CHANNELS)
508 {
510 }
511 if (!voter_channel_count_matches_strategy(voter, buffer_count))
512 {
514 }
515 for (i = 0U; i < buffer_count; i++)
516 {
517 if (buffers[i] == NULL)
518 {
520 }
521 }
522
523 required_quorum = voter_required_quorum(voter);
524
525 for (i = 0U; i < buffer_count; i++)
526 {
527 (void)memcpy(local_buffers[i], buffers[i], data_size);
528 responded[i] = true;
529 group_count[i] = 0U;
530 }
531
532 voter_group_matching_responses(voter, buffer_count, (const uint8_t(*)[RTE_VOTER_MAX_MESSAGE_SIZE])local_buffers,
533 responded, data_size, group_id, group_count, &num_groups);
534
535 for (i = 0U; i < num_groups; i++)
536 {
537 if (group_count[i] > best_count)
538 {
539 best_count = group_count[i];
540 best_group = i;
541 }
542 }
543
544 if (best_count >= required_quorum)
545 {
546 local_result = RTE_VOTING_AGREED;
547 }
548 else
549 {
550 local_result = RTE_VOTING_DISAGREED;
551 }
552
553 if (result != NULL)
554 {
555 *result = local_result;
556 }
557
558 if (local_result == RTE_VOTING_AGREED)
559 {
560 for (i = 0U; i < buffer_count; i++)
561 {
562 if (responded[i] && (group_id[i] == best_group))
563 {
564 if (out_data != NULL)
565 {
566 (void)memcpy(out_data, local_buffers[i], data_size);
567 }
568 break;
569 }
570 }
571 if (out_size != NULL)
572 {
573 *out_size = data_size;
574 }
575 return RTE_STATUS_OK;
576 }
577
578 if (out_size != NULL)
579 {
580 *out_size = 0U;
581 }
582
583 /* Identical DISAGREED handling to rte_voter_receive() (REQ-VOTER-005) - deliberately kept
584 * as its own small copy here rather than factored into a shared helper, to avoid touching
585 * rte_voter_receive()'s own body any further than the two explicit-count parameterizations
586 * above (voter_channel_count_matches_strategy()/voter_group_matching_responses()) - this is
587 * vital, heavily-relied-upon code and the duplicated block is short enough that the lower
588 * blast radius was judged worth it. */
589 voter->total_disagreements++;
590 if (voter->config.log_disagreements)
591 {
592 rte_log_write(RTE_LOG_LEVEL_ERROR, "voter", "channels disagreed (buffers)");
593 }
594 if (voter->config.on_disagreement != NULL)
595 {
596 voter->config.on_disagreement(voter->config.disagreement_context, local_result);
597 }
598 RTE_SAFESTATE(voter->config.safestate_level, voter->config.safestate_reason);
599
601}
602
604 uint32_t *healthy_count,
605 uint32_t *total_disagreements)
606{
607 if (voter == NULL)
608 {
610 }
611 if (healthy_count != NULL)
612 {
613 *healthy_count = voter_count_healthy(voter);
614 }
615 if (total_disagreements != NULL)
616 {
617 *total_disagreements = voter->total_disagreements;
618 }
619 return RTE_STATUS_OK;
620}
621
623{
624 return (voter != NULL) ? voter->channel_count : 0U;
625}
626
627rte_channel_t *rte_voter_get_channel(const rte_voter_t *voter, uint32_t index)
628{
629 if ((voter == NULL) || (index >= voter->channel_count))
630 {
631 return NULL;
632 }
633 return voter->channels[index];
634}
635
637{
638 uint32_t i;
639
640 if ((voter == NULL) || (name == NULL))
641 {
642 return NULL;
643 }
644 for (i = 0U; i < voter->channel_count; i++)
645 {
646 const char *channel_name = rte_channel_get_name(voter->channels[i]);
647
648 if ((channel_name != NULL) && (strcmp(channel_name, name) == 0))
649 {
650 return voter->channels[i];
651 }
652 }
653 return NULL;
654}
655
657{
658 if (voter == NULL)
659 {
660 return RTE_STATUS_OK;
661 }
662 voter->initialized = false;
663 return RTE_STATUS_OK;
664}
rte_status_t rte_lifecycle_check_setup_allowed(void)
Convenience check for a setup-only constructor: call this as one of the first checks in any function ...
void rte_log_write(rte_log_level_t level, const char *tag, const char *message)
Emits one log message. Non-blocking; never fails the caller's control flow even if the message is dro...
Definition rte_log.c:108
@ RTE_LOG_LEVEL_ERROR
Definition rte_log.h:38
#define RTE_SAFESTATE(level, reason)
Explicitly enters the given safe-state level with a reason code, capturing the call site automaticall...
rte_status_t
Common result/status codes.
Definition rte_status.h:27
@ RTE_STATUS_OK
Definition rte_status.h:28
@ RTE_STATUS_INVALID_PARAM
Definition rte_status.h:29
@ RTE_STATUS_NOT_INITIALIZED
Definition rte_status.h:30
@ RTE_STATUS_HARDWARE_FAULT
Definition rte_status.h:36
@ RTE_STATUS_RESOURCE_EXHAUSTED
Definition rte_status.h:33
@ RTE_STATUS_TIMEOUT
Definition rte_status.h:32
rte_voter_storage_t rte_voter_t
Opaque handle to a voter instance.
Definition rte_voter.h:149
rte_status_t rte_voter_get_aggregated_health(const rte_voter_t *voter, uint32_t *healthy_count, uint32_t *total_disagreements)
Aggregated health across every registered channel.
Definition rte_voter.c:603
#define RTE_VOTER_MAX_MESSAGE_SIZE
Maximum payload size rte_voter_send()/_receive() supports.
Definition rte_voter.h:135
#define RTE_VOTER_MAX_CHANNELS
Maximum number of channels a single voter can register.
Definition rte_voter.h:132
rte_status_t rte_voter_register_channel(rte_voter_t *voter, rte_channel_t *channel)
Registers one already-initialized channel with this voter.
Definition rte_voter.c:240
rte_channel_t * rte_voter_get_channel(const rte_voter_t *voter, uint32_t index)
Direct access to one registered channel, by index.
Definition rte_voter.c:627
rte_status_t rte_voter_destroy(rte_voter_t *voter)
Destroys a voter instance.
Definition rte_voter.c:656
rte_status_t rte_voter_vote_buffers(rte_voter_t *voter, const void *const *buffers, uint32_t buffer_count, size_t data_size, rte_voting_result_t *result, void *out_data, size_t *out_size)
N-way vote on caller-collected buffers, without any registered-channel I/O (ADR-039's N-way cross-com...
Definition rte_voter.c:480
rte_voting_result_t
Outcome of one rte_voter_receive() call.
Definition rte_voter.h:54
rte_status_t rte_voter_send(rte_voter_t *voter, const void *data, size_t data_size)
Broadcasts data to every registered, healthy channel.
Definition rte_voter.c:269
uint32_t rte_voter_get_channel_count(const rte_voter_t *voter)
Number of channels currently registered with this voter.
Definition rte_voter.c:622
rte_channel_t * rte_voter_get_channel_by_name(const rte_voter_t *voter, const char *name)
Direct access to one registered channel, by name (e.g. "ChannelAtoB") - the name each channel was giv...
Definition rte_voter.c:636
rte_status_t rte_voter_init(rte_voter_storage_t *storage, const rte_voter_config_t *config)
Initializes a voter with zero registered channels.
Definition rte_voter.c:201
rte_status_t rte_voter_receive(rte_voter_t *voter, void *data, size_t data_size, rte_voting_result_t *result, size_t *bytes_received)
Receives from every registered, healthy channel and votes.
Definition rte_voter.c:331
@ RTE_VOTING_NMR
Definition rte_voter.h:48
@ RTE_VOTING_2OO3
Definition rte_voter.h:46
@ RTE_VOTING_2OO2
Definition rte_voter.h:44
@ RTE_VOTING_AGREED
Definition rte_voter.h:56
@ RTE_VOTING_INSUFFICIENT_QUORUM
Definition rte_voter.h:62
@ RTE_VOTING_DISAGREED
Definition rte_voter.h:58
@ RTE_VOTING_TIMEOUT
Definition rte_voter.h:60
Process-wide application setup-phase lock (ADR-026).
OS Abstraction Layer - Logging/diagnostics service.
Safe-state transitions and checked assertions (ADR-004).
N-way voting across registered rte_channel_t links (2oo2/2oo3/NMR).
Health statistics for one vital channel.
Definition rte_channel.h:39
Configuration for rte_voter_init().
Definition rte_voter.h:87
rte_safestate_level_t safestate_level
Definition rte_voter.h:113
rte_voter_compare_fn compare
Definition rte_voter.h:96
void * disagreement_context
Definition rte_voter.h:128
uint32_t quorum_size
Definition rte_voter.h:92
uint32_t channel_timeout_ms
Definition rte_voter.h:94
rte_safestate_reason_t safestate_reason
Definition rte_voter.h:115
void * compare_context
Definition rte_voter.h:98
void(*) on_disagreement(void *context, rte_voting_result_t result)
Definition rte_voter.h:126
rte_voting_strategy_t voting_strategy
Definition rte_voter.h:89
Storage for one voter instance (opaque to caller). No dynamic memory.
Definition rte_voter.h:140