16static bool voter_channel_count_matches_strategy(
const sapi_voter_t *voter)
23 ok = (voter->channel_count == 2U);
26 ok = (voter->channel_count == 3U);
29 ok = (voter->channel_count >= 1U) &&
31 (voter->config.
quorum_size <= voter->channel_count);
40static uint32_t voter_required_quorum(
const sapi_voter_t *voter)
65static bool voter_data_equal(
const sapi_voter_t *voter,
const void *a,
const void *b,
size_t size)
69 if (voter->config.
compare != NULL)
75 equal = (memcmp(a, b, size) == 0);
80static uint32_t voter_count_healthy(
const sapi_voter_t *voter)
83 uint32_t healthy = 0U;
86 for (i = 0U; i < voter->channel_count; i++)
101 if ((storage == NULL) || (config == NULL))
110 return lifecycle_status;
128 storage->config = *config;
129 storage->channel_count = 0U;
130 storage->total_disagreements = 0U;
131 storage->initialized =
true;
140 if ((voter == NULL) || (channel == NULL))
144 if (!voter->initialized)
153 return lifecycle_status;
160 voter->channels[voter->channel_count] = channel;
161 voter->channel_count++;
168 uint32_t healthy_count = 0U;
169 bool any_failed =
false;
170 bool any_timeout =
false;
173 if ((voter == NULL) || (data == NULL) || (data_size == 0U))
177 if (!voter->initialized)
185 if (!voter_channel_count_matches_strategy(voter))
190 for (i = 0U; i < voter->channel_count; i++)
212 if (healthy_count == 0U)
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;
244 if ((voter == NULL) || (data == NULL) || (data_size == 0U))
248 if (!voter->initialized)
256 if (!voter_channel_count_matches_strategy(voter))
261 required_quorum = voter_required_quorum(voter);
263 for (i = 0U; i < voter->channel_count; i++)
265 responded[i] =
false;
269 if (voter_count_healthy(voter) < required_quorum)
275 for (i = 0U; i < voter->channel_count; i++)
304 if (successful == 0U)
308 else if (successful < required_quorum)
314 for (i = 0U; i < voter->channel_count; i++)
322 for (j = 0U; j < i; j++)
328 if (voter_data_equal(voter, buffers[i], buffers[j], data_size))
330 group_id[i] = group_id[j];
331 group_count[group_id[i]]++;
338 group_id[i] = num_groups;
339 group_count[num_groups] = 1U;
344 for (i = 0U; i < num_groups; i++)
346 if (group_count[i] > best_count)
348 best_count = group_count[i];
353 if (best_count >= required_quorum)
355 for (i = 0U; i < voter->channel_count; i++)
357 if (responded[i] && (group_id[i] == best_group))
359 (void)memcpy(data, buffers[i], data_size);
374 *result = local_result;
379 if (bytes_received != NULL)
381 *bytes_received = data_size;
386 if (bytes_received != NULL)
388 *bytes_received = 0U;
393 voter->total_disagreements++;
413 uint32_t *healthy_count,
414 uint32_t *total_disagreements)
420 if (healthy_count != NULL)
422 *healthy_count = voter_count_healthy(voter);
424 if (total_disagreements != NULL)
426 *total_disagreements = voter->total_disagreements;
433 return (voter != NULL) ? voter->channel_count : 0U;
438 if ((voter == NULL) || (index >= voter->channel_count))
442 return voter->channels[index];
449 if ((voter == NULL) || (name == NULL))
453 for (i = 0U; i < voter->channel_count; i++)
457 if ((channel_name != NULL) && (strcmp(channel_name, name) == 0))
459 return voter->channels[i];
471 voter->initialized =
false;
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...
#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.
@ SAPI_STATUS_HARDWARE_FAULT
@ SAPI_STATUS_INVALID_PARAM
@ SAPI_STATUS_RESOURCE_EXHAUSTED
@ SAPI_STATUS_NOT_INITIALIZED
sapi_status_t sapi_channel_get_health(const sapi_channel_t *handle, sapi_channel_health_t *out_health)
Queries this channel's health statistics.
const char * sapi_channel_get_name(const sapi_channel_t *handle)
Returns this channel's name, as given in sapi_channel_config_t::name at sapi_channel_init() time.
sapi_status_t sapi_channel_send(sapi_channel_t *handle, const void *data, size_t data_size)
Sends data on this channel via its backend send callback.
sapi_status_t sapi_channel_receive(sapi_channel_t *handle, void *data, size_t data_size, uint32_t timeout_ms)
Receives data on this channel via its backend receive callback.
sapi_channel_storage_t sapi_channel_t
Opaque handle to a vital channel instance.
sapi_status_t sapi_voter_destroy(sapi_voter_t *voter)
Destroys a voter instance.
sapi_voter_storage_t sapi_voter_t
Opaque handle to a voter instance.
sapi_voting_result_t
Outcome of one sapi_voter_receive() call.
uint32_t sapi_voter_get_channel_count(const sapi_voter_t *voter)
Number of channels currently registered with this voter.
sapi_status_t sapi_voter_register_channel(sapi_voter_t *voter, sapi_channel_t *channel)
Registers one already-initialized channel with this voter.
sapi_channel_t * sapi_voter_get_channel(const sapi_voter_t *voter, uint32_t index)
Direct access to one registered channel, by index.
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...
sapi_status_t sapi_voter_send(sapi_voter_t *voter, const void *data, size_t data_size)
Broadcasts data to every registered, healthy channel.
sapi_status_t sapi_voter_init(sapi_voter_storage_t *storage, const sapi_voter_config_t *config)
Initializes a voter with zero registered channels.
#define SAPI_VOTER_MAX_MESSAGE_SIZE
Maximum payload size sapi_voter_send()/_receive() supports.
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.
#define SAPI_VOTER_MAX_CHANNELS
Maximum number of channels a single voter can register.
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.
@ SAPI_VOTING_INSUFFICIENT_QUORUM
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().
sapi_voter_compare_fn compare
bool trigger_safestate_on_disagreement
sapi_voting_strategy_t voting_strategy
void(*) on_disagreement(void *context, sapi_voting_result_t result)
uint32_t channel_timeout_ms
void * disagreement_context
Storage for one voter instance (opaque to caller). No dynamic memory.