Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
sapi_voter.c
Go to the documentation of this file.
1
9#include <string.h>
10
15
16static bool voter_channel_count_matches_strategy(const sapi_voter_t *voter)
17{
18 bool ok;
19
20 switch (voter->config.voting_strategy)
21 {
23 ok = (voter->channel_count == 2U);
24 break;
26 ok = (voter->channel_count == 3U);
27 break;
28 case SAPI_VOTING_NMR:
29 ok = (voter->channel_count >= 1U) &&
30 (voter->config.quorum_size >= 1U) &&
31 (voter->config.quorum_size <= voter->channel_count);
32 break;
33 default:
34 ok = false;
35 break;
36 }
37 return ok;
38}
39
40static uint32_t voter_required_quorum(const sapi_voter_t *voter)
41{
42 uint32_t quorum;
43
44 switch (voter->config.voting_strategy)
45 {
47 quorum = 2U;
48 break;
50 quorum = 2U;
51 break;
52 case SAPI_VOTING_NMR:
53 quorum = voter->config.quorum_size;
54 break;
55 default:
56 /* Defensive: sapi_voter_init() already rejects any other
57 * value, so a live voter's strategy is always one of the
58 * three above. */
59 quorum = 0xFFFFFFFFU;
60 break;
61 }
62 return quorum;
63}
64
65static bool voter_data_equal(const sapi_voter_t *voter, const void *a, const void *b, size_t size)
66{
67 bool equal;
68
69 if (voter->config.compare != NULL)
70 {
71 equal = voter->config.compare(a, b, size, voter->config.compare_context);
72 }
73 else
74 {
75 equal = (memcmp(a, b, size) == 0);
76 }
77 return equal;
78}
79
80static uint32_t voter_count_healthy(const sapi_voter_t *voter)
81{
82 uint32_t i;
83 uint32_t healthy = 0U;
85
86 for (i = 0U; i < voter->channel_count; i++)
87 {
88 (void)sapi_channel_get_health(voter->channels[i], &h);
89 if (h.is_healthy)
90 {
91 healthy++;
92 }
93 }
94 return healthy;
95}
96
98{
99 sapi_status_t lifecycle_status;
100
101 if ((storage == NULL) || (config == NULL))
102 {
104 }
105 /* REQ-LIFECYCLE-001 (ADR-026): a voter is a setup-only resource - refuse once the
106 * application's setup phase has been locked. */
107 lifecycle_status = sapi_lifecycle_check_setup_allowed();
108 if (lifecycle_status != SAPI_STATUS_OK)
109 {
110 return lifecycle_status;
111 }
112
113 switch (config->voting_strategy)
114 {
115 case SAPI_VOTING_2OO2:
116 case SAPI_VOTING_2OO3:
117 break;
118 case SAPI_VOTING_NMR:
119 if (config->quorum_size == 0U)
120 {
122 }
123 break;
124 default:
126 }
127
128 storage->config = *config;
129 storage->channel_count = 0U;
130 storage->total_disagreements = 0U;
131 storage->initialized = true;
132
133 return SAPI_STATUS_OK;
134}
135
137{
138 sapi_status_t lifecycle_status;
139
140 if ((voter == NULL) || (channel == NULL))
141 {
143 }
144 if (!voter->initialized)
145 {
147 }
148 /* REQ-LIFECYCLE-001 (ADR-026): registering a channel into a voter is setup-only - refuse
149 * once the application's setup phase has been locked. */
150 lifecycle_status = sapi_lifecycle_check_setup_allowed();
151 if (lifecycle_status != SAPI_STATUS_OK)
152 {
153 return lifecycle_status;
154 }
155 if (voter->channel_count >= SAPI_VOTER_MAX_CHANNELS)
156 {
158 }
159
160 voter->channels[voter->channel_count] = channel;
161 voter->channel_count++;
162 return SAPI_STATUS_OK;
163}
164
165sapi_status_t sapi_voter_send(sapi_voter_t *voter, const void *data, size_t data_size)
166{
167 uint32_t i;
168 uint32_t healthy_count = 0U;
169 bool any_failed = false;
170 bool any_timeout = false;
172
173 if ((voter == NULL) || (data == NULL) || (data_size == 0U))
174 {
176 }
177 if (!voter->initialized)
178 {
180 }
181 if (data_size > SAPI_VOTER_MAX_MESSAGE_SIZE)
182 {
184 }
185 if (!voter_channel_count_matches_strategy(voter))
186 {
188 }
189
190 for (i = 0U; i < voter->channel_count; i++)
191 {
192 sapi_status_t st;
193
194 (void)sapi_channel_get_health(voter->channels[i], &h);
195 if (!h.is_healthy)
196 {
197 continue;
198 }
199 healthy_count++;
200
201 st = sapi_channel_send(voter->channels[i], data, data_size);
202 if (st != SAPI_STATUS_OK)
203 {
204 any_failed = true;
205 if (st == SAPI_STATUS_TIMEOUT)
206 {
207 any_timeout = true;
208 }
209 }
210 }
211
212 if (healthy_count == 0U)
213 {
215 }
216 if (any_timeout)
217 {
218 return SAPI_STATUS_TIMEOUT;
219 }
220 if (any_failed)
221 {
223 }
224 return SAPI_STATUS_OK;
225}
226
227sapi_status_t sapi_voter_receive(sapi_voter_t *voter, void *data, size_t data_size,
228 sapi_voting_result_t *result, size_t *bytes_received)
229{
231 bool responded[SAPI_VOTER_MAX_CHANNELS];
232 uint32_t group_id[SAPI_VOTER_MAX_CHANNELS];
233 uint32_t group_count[SAPI_VOTER_MAX_CHANNELS];
234 uint32_t num_groups = 0U;
235 uint32_t successful = 0U;
236 uint32_t required_quorum;
237 uint32_t best_group = 0U;
238 uint32_t best_count = 0U;
239 bool saw_timeout = false;
240 sapi_voting_result_t local_result;
241 uint32_t i;
242 uint32_t j;
243
244 if ((voter == NULL) || (data == NULL) || (data_size == 0U))
245 {
247 }
248 if (!voter->initialized)
249 {
251 }
252 if (data_size > SAPI_VOTER_MAX_MESSAGE_SIZE)
253 {
255 }
256 if (!voter_channel_count_matches_strategy(voter))
257 {
259 }
260
261 required_quorum = voter_required_quorum(voter);
262
263 for (i = 0U; i < voter->channel_count; i++)
264 {
265 responded[i] = false;
266 group_count[i] = 0U;
267 }
268
269 if (voter_count_healthy(voter) < required_quorum)
270 {
271 local_result = SAPI_VOTING_INSUFFICIENT_QUORUM;
272 }
273 else
274 {
275 for (i = 0U; i < voter->channel_count; i++)
276 {
278 sapi_status_t st;
279
280 (void)sapi_channel_get_health(voter->channels[i], &h);
281 if (!h.is_healthy)
282 {
283 continue;
284 }
285
286 st = sapi_channel_receive(voter->channels[i], buffers[i], data_size,
287 voter->config.channel_timeout_ms);
288 if (st == SAPI_STATUS_OK)
289 {
290 responded[i] = true;
291 successful++;
292 }
293 else if (st == SAPI_STATUS_TIMEOUT)
294 {
295 saw_timeout = true;
296 }
297 else
298 {
299 /* Any other failure just doesn't count toward successful;
300 * saw_timeout stays as-is. */
301 }
302 }
303
304 if (successful == 0U)
305 {
306 local_result = saw_timeout ? SAPI_VOTING_TIMEOUT : SAPI_VOTING_INSUFFICIENT_QUORUM;
307 }
308 else if (successful < required_quorum)
309 {
310 local_result = SAPI_VOTING_INSUFFICIENT_QUORUM;
311 }
312 else
313 {
314 for (i = 0U; i < voter->channel_count; i++)
315 {
316 bool placed = false;
317
318 if (!responded[i])
319 {
320 continue;
321 }
322 for (j = 0U; j < i; j++)
323 {
324 if (!responded[j])
325 {
326 continue;
327 }
328 if (voter_data_equal(voter, buffers[i], buffers[j], data_size))
329 {
330 group_id[i] = group_id[j];
331 group_count[group_id[i]]++;
332 placed = true;
333 break;
334 }
335 }
336 if (!placed)
337 {
338 group_id[i] = num_groups;
339 group_count[num_groups] = 1U;
340 num_groups++;
341 }
342 }
343
344 for (i = 0U; i < num_groups; i++)
345 {
346 if (group_count[i] > best_count)
347 {
348 best_count = group_count[i];
349 best_group = i;
350 }
351 }
352
353 if (best_count >= required_quorum)
354 {
355 for (i = 0U; i < voter->channel_count; i++)
356 {
357 if (responded[i] && (group_id[i] == best_group))
358 {
359 (void)memcpy(data, buffers[i], data_size);
360 break;
361 }
362 }
363 local_result = SAPI_VOTING_AGREED;
364 }
365 else
366 {
367 local_result = SAPI_VOTING_DISAGREED;
368 }
369 }
370 }
371
372 if (result != NULL)
373 {
374 *result = local_result;
375 }
376
377 if (local_result == SAPI_VOTING_AGREED)
378 {
379 if (bytes_received != NULL)
380 {
381 *bytes_received = data_size;
382 }
383 return SAPI_STATUS_OK;
384 }
385
386 if (bytes_received != NULL)
387 {
388 *bytes_received = 0U;
389 }
390
391 if (local_result == SAPI_VOTING_DISAGREED)
392 {
393 voter->total_disagreements++;
394 if (voter->config.log_disagreements)
395 {
396 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "voter", "channels disagreed");
397 }
398 if (voter->config.trigger_safestate_on_disagreement)
399 {
401 }
402 }
403
404 if (voter->config.on_disagreement != NULL)
405 {
406 voter->config.on_disagreement(voter->config.disagreement_context, local_result);
407 }
408
410}
411
413 uint32_t *healthy_count,
414 uint32_t *total_disagreements)
415{
416 if (voter == NULL)
417 {
419 }
420 if (healthy_count != NULL)
421 {
422 *healthy_count = voter_count_healthy(voter);
423 }
424 if (total_disagreements != NULL)
425 {
426 *total_disagreements = voter->total_disagreements;
427 }
428 return SAPI_STATUS_OK;
429}
430
432{
433 return (voter != NULL) ? voter->channel_count : 0U;
434}
435
437{
438 if ((voter == NULL) || (index >= voter->channel_count))
439 {
440 return NULL;
441 }
442 return voter->channels[index];
443}
444
446{
447 uint32_t i;
448
449 if ((voter == NULL) || (name == NULL))
450 {
451 return NULL;
452 }
453 for (i = 0U; i < voter->channel_count; i++)
454 {
455 const char *channel_name = sapi_channel_get_name(voter->channels[i]);
456
457 if ((channel_name != NULL) && (strcmp(channel_name, name) == 0))
458 {
459 return voter->channels[i];
460 }
461 }
462 return NULL;
463}
464
466{
467 if (voter == NULL)
468 {
469 return SAPI_STATUS_OK;
470 }
471 voter->initialized = false;
472 return SAPI_STATUS_OK;
473}
sapi_status_t sapi_lifecycle_check_setup_allowed(void)
Convenience check for a setup-only constructor: call this as one of the first checks in any function ...
void sapi_log_write(sapi_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 sapi_log.c:108
@ SAPI_LOG_LEVEL_ERROR
Definition sapi_log.h:38
#define SAPI_SAFESTATE(level, reason)
Explicitly enters the given safe-state level with a reason code, capturing the call site automaticall...
#define SAPI_SAFESTATE_REASON_UNSPECIFIED
@ SAPI_SAFESTATE_LEVEL_SAFE
sapi_status_t
Common result/status codes.
Definition sapi_status.h:27
@ SAPI_STATUS_HARDWARE_FAULT
Definition sapi_status.h:36
@ SAPI_STATUS_TIMEOUT
Definition sapi_status.h:32
@ SAPI_STATUS_INVALID_PARAM
Definition sapi_status.h:29
@ SAPI_STATUS_RESOURCE_EXHAUSTED
Definition sapi_status.h:33
@ SAPI_STATUS_NOT_INITIALIZED
Definition sapi_status.h:30
@ SAPI_STATUS_OK
Definition sapi_status.h:28
sapi_status_t sapi_voter_destroy(sapi_voter_t *voter)
Destroys a voter instance.
Definition sapi_voter.c:465
sapi_voter_storage_t sapi_voter_t
Opaque handle to a voter instance.
Definition sapi_voter.h:128
sapi_voting_result_t
Outcome of one sapi_voter_receive() call.
Definition sapi_voter.h:45
uint32_t sapi_voter_get_channel_count(const sapi_voter_t *voter)
Number of channels currently registered with this voter.
Definition sapi_voter.c:431
sapi_status_t sapi_voter_register_channel(sapi_voter_t *voter, sapi_channel_t *channel)
Registers one already-initialized channel with this voter.
Definition sapi_voter.c:136
sapi_channel_t * sapi_voter_get_channel(const sapi_voter_t *voter, uint32_t index)
Direct access to one registered channel, by index.
Definition sapi_voter.c:436
sapi_channel_t * sapi_voter_get_channel_by_name(const sapi_voter_t *voter, const char *name)
Direct access to one registered channel, by name (e.g. "ChannelAtoB") - the name each channel was giv...
Definition sapi_voter.c:445
sapi_status_t sapi_voter_send(sapi_voter_t *voter, const void *data, size_t data_size)
Broadcasts data to every registered, healthy channel.
Definition sapi_voter.c:165
sapi_status_t sapi_voter_init(sapi_voter_storage_t *storage, const sapi_voter_config_t *config)
Initializes a voter with zero registered channels.
Definition sapi_voter.c:97
#define SAPI_VOTER_MAX_MESSAGE_SIZE
Maximum payload size sapi_voter_send()/_receive() supports.
Definition sapi_voter.h:114
sapi_status_t sapi_voter_receive(sapi_voter_t *voter, void *data, size_t data_size, sapi_voting_result_t *result, size_t *bytes_received)
Receives from every registered, healthy channel and votes.
Definition sapi_voter.c:227
#define SAPI_VOTER_MAX_CHANNELS
Maximum number of channels a single voter can register.
Definition sapi_voter.h:111
sapi_status_t sapi_voter_get_aggregated_health(const sapi_voter_t *voter, uint32_t *healthy_count, uint32_t *total_disagreements)
Aggregated health across every registered channel.
Definition sapi_voter.c:412
@ SAPI_VOTING_DISAGREED
Definition sapi_voter.h:49
@ SAPI_VOTING_INSUFFICIENT_QUORUM
Definition sapi_voter.h:53
@ SAPI_VOTING_TIMEOUT
Definition sapi_voter.h:51
@ SAPI_VOTING_AGREED
Definition sapi_voter.h:47
@ SAPI_VOTING_2OO3
Definition sapi_voter.h:37
@ SAPI_VOTING_NMR
Definition sapi_voter.h:39
@ SAPI_VOTING_2OO2
Definition sapi_voter.h:35
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 sapi_channel_t links (2oo2/2oo3/NMR).
Health statistics for one vital channel.
Configuration for sapi_voter_init().
Definition sapi_voter.h:78
sapi_voter_compare_fn compare
Definition sapi_voter.h:87
bool trigger_safestate_on_disagreement
Definition sapi_voter.h:98
sapi_voting_strategy_t voting_strategy
Definition sapi_voter.h:80
void(*) on_disagreement(void *context, sapi_voting_result_t result)
Definition sapi_voter.h:105
uint32_t channel_timeout_ms
Definition sapi_voter.h:85
void * disagreement_context
Definition sapi_voter.h:107
Storage for one voter instance (opaque to caller). No dynamic memory.
Definition sapi_voter.h:119