Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
sapi_safechannel.c
Go to the documentation of this file.
1
8
10
11#include <string.h>
12
16static void safechannel_close_links(sapi_safechannel_t *channel, uint32_t count)
17{
18 uint32_t i;
19
20 for (i = 0U; i < count; i++)
21 {
22 if (channel->link_handles[i] != NULL)
23 {
24 (void)sapi_netlink_close(channel->link_handles[i]);
25 channel->link_handles[i] = NULL;
26 }
27 }
28}
29
34static sapi_status_t safechannel_open_one_endpoint(sapi_netlink_storage_t *storage,
35 const sapi_safechannel_endpoint_t *endpoint, size_t message_size,
36 sapi_duration_ms_t connect_timeout_ms,
37 sapi_netlink_handle_t *out_handle)
38{
40 sapi_status_t status;
41
42 memset(&cfg, 0, sizeof(cfg));
43 cfg.role = endpoint->role;
44 cfg.host = endpoint->host;
45 cfg.port = endpoint->port;
46 cfg.message_size = message_size;
47 cfg.connect_timeout_ms = connect_timeout_ms;
48
49 /* sapi_netlink_open() itself already bounds a single attempt to
50 * connect_timeout_ms (REQ-OAL-NETLINK-002); a LISTEN-role endpoint
51 * blocks until accepted or that same timeout, so one attempt is
52 * sufficient there. A CONNECT-role endpoint can fail fast if the
53 * peer isn't listening yet, so this retries for the same overall
54 * budget rather than failing on the first attempt. */
55 status = sapi_netlink_open(storage, &cfg, out_handle);
56 return status;
57}
58
59static sapi_status_t safechannel_open_dual(sapi_safechannel_t *channel, const sapi_safechannel_dual_config_t *cfg)
60{
62 sapi_status_t status;
63 uint32_t i;
64
65 if ((cfg->link_count == 0U) || (cfg->link_count > SAPI_SAFECHANNEL_MAX_LINKS))
66 {
68 }
69 for (i = 0U; i < cfg->link_count; i++)
70 {
71 if ((cfg->endpoints[i].role == SAPI_NETLINK_ROLE_CONNECT) && (cfg->endpoints[i].host == NULL))
72 {
74 }
75 }
76
77 for (i = 0U; i < cfg->link_count; i++)
78 {
79 status = safechannel_open_one_endpoint(&channel->links[i], &cfg->endpoints[i], sizeof(sapi_vital_message_t),
80 cfg->connect_timeout_ms, &channel->link_handles[i]);
81 if (status != SAPI_STATUS_OK)
82 {
83 safechannel_close_links(channel, i);
84 return status;
85 }
86 }
87 channel->link_count = cfg->link_count;
88
89 memset(&dual_cfg, 0, sizeof(dual_cfg));
90 for (i = 0U; i < cfg->link_count; i++)
91 {
92 dual_cfg.links[i] = channel->link_handles[i];
93 }
94 dual_cfg.link_count = cfg->link_count;
95 dual_cfg.sender_id = cfg->sender_id;
96 dual_cfg.expected_peer_id = cfg->expected_peer_id;
97 dual_cfg.ack_timeout_ms = cfg->ack_timeout_ms;
98 dual_cfg.status_callback = cfg->status_callback;
100
101 status = sapi_dual_channel_init(&channel->impl.dual, &dual_cfg);
102 if (status != SAPI_STATUS_OK)
103 {
104 safechannel_close_links(channel, channel->link_count);
105 channel->link_count = 0U;
106 return status;
107 }
108 return SAPI_STATUS_OK;
109}
110
117static sapi_status_t safechannel_vital_backend_send(void *channel_handle, const void *data, size_t data_size)
118{
119 /* Fixed, generous per-op timeout: the voter's own
120 * config->channel_timeout_ms already bounds the overall voting
121 * round from the caller's perspective (sapi_voter_send() calls this
122 * once per registered channel); this per-send value only bounds one
123 * individual transport call within that round. */
124 return sapi_netlink_send((sapi_netlink_handle_t)channel_handle, data, data_size, 5000U);
125}
126
129static sapi_status_t safechannel_vital_backend_recv(void *channel_handle, void *data, size_t data_size,
130 uint32_t timeout_ms)
131{
132 return sapi_netlink_receive((sapi_netlink_handle_t)channel_handle, data, data_size,
133 (sapi_duration_ms_t)timeout_ms);
134}
135
136static sapi_status_t safechannel_open_vital(sapi_safechannel_t *channel, const sapi_safechannel_vital_config_t *cfg)
137{
138 sapi_voter_config_t voter_cfg;
139 sapi_status_t status;
140 uint32_t i;
141
142 if ((cfg->link_count == 0U) || (cfg->link_count > SAPI_SAFECHANNEL_MAX_LINKS) || (cfg->message_size == 0U))
143 {
145 }
146 for (i = 0U; i < cfg->link_count; i++)
147 {
148 if ((cfg->endpoints[i].role == SAPI_NETLINK_ROLE_CONNECT) && (cfg->endpoints[i].host == NULL))
149 {
151 }
152 }
153
154 for (i = 0U; i < cfg->link_count; i++)
155 {
156 status = safechannel_open_one_endpoint(&channel->links[i], &cfg->endpoints[i], cfg->message_size,
157 cfg->connect_timeout_ms, &channel->link_handles[i]);
158 if (status != SAPI_STATUS_OK)
159 {
160 safechannel_close_links(channel, i);
161 return status;
162 }
163 }
164 channel->link_count = cfg->link_count;
165
166 memset(&voter_cfg, 0, sizeof(voter_cfg));
167 voter_cfg.voting_strategy = cfg->voting_strategy;
168 voter_cfg.quorum_size = cfg->quorum_size;
169 voter_cfg.channel_timeout_ms = (uint32_t)cfg->channel_timeout_ms;
170 voter_cfg.log_disagreements = cfg->log_disagreements;
171 voter_cfg.trigger_safestate_on_disagreement = true;
172 voter_cfg.on_disagreement = cfg->on_disagreement;
173 voter_cfg.disagreement_context = cfg->context;
174
175 status = sapi_voter_init(&channel->impl.vital.voter, &voter_cfg);
176 if (status != SAPI_STATUS_OK)
177 {
178 safechannel_close_links(channel, channel->link_count);
179 channel->link_count = 0U;
180 return status;
181 }
182
183 for (i = 0U; i < cfg->link_count; i++)
184 {
185 sapi_channel_config_t chan_cfg;
186
187 memset(&chan_cfg, 0, sizeof(chan_cfg));
188 chan_cfg.channel_handle = (void *)channel->link_handles[i];
191
192 status = sapi_channel_init(&channel->impl.vital.channels[i], &chan_cfg);
193 if (status == SAPI_STATUS_OK)
194 {
195 status = sapi_voter_register_channel(&channel->impl.vital.voter, &channel->impl.vital.channels[i]);
196 }
197 if (status != SAPI_STATUS_OK)
198 {
199 safechannel_close_links(channel, channel->link_count);
200 channel->link_count = 0U;
201 return status;
202 }
203 }
204
205 return SAPI_STATUS_OK;
206}
207
209{
210 sapi_status_t status;
211
212 if ((channel == NULL) || (config == NULL))
213 {
215 }
216 memset(channel, 0, sizeof(*channel));
217 channel->type = config->type;
218
219 switch (config->type)
220 {
222 status = safechannel_open_dual(channel, &config->as.dual);
223 break;
225 status = safechannel_open_vital(channel, &config->as.vital);
226 break;
227 default:
229 break;
230 }
231
232 channel->is_open = (status == SAPI_STATUS_OK);
233 return status;
234}
235
236sapi_status_t sapi_safechannel_send(sapi_safechannel_t *channel, const uint8_t *payload, size_t payload_size)
237{
238 if ((channel == NULL) || (!channel->is_open))
239 {
241 }
242
243 if (channel->type == SAPI_SAFECHANNEL_TYPE_DUAL_REDUNDANT)
244 {
245 if (payload_size > (size_t)UINT8_MAX)
246 {
248 }
249 return sapi_dual_channel_send(&channel->impl.dual, payload, (uint8_t)payload_size, NULL);
250 }
251 return sapi_voter_send(&channel->impl.vital.voter, payload, payload_size);
252}
253
254sapi_status_t sapi_safechannel_receive(sapi_safechannel_t *channel, uint8_t *out_payload, size_t max_size,
255 sapi_duration_ms_t timeout_ms, size_t *out_size)
256{
257 if ((channel == NULL) || (!channel->is_open) || (out_size == NULL))
258 {
260 }
261
262 if (channel->type == SAPI_SAFECHANNEL_TYPE_DUAL_REDUNDANT)
263 {
264 uint8_t received_size = 0U;
265 sapi_status_t status;
266
267 if (max_size > (size_t)UINT8_MAX)
268 {
269 max_size = (size_t)UINT8_MAX;
270 }
271 status = sapi_dual_channel_receive(&channel->impl.dual, out_payload, (uint8_t)max_size, timeout_ms,
272 &received_size);
273 *out_size = (size_t)received_size;
274 return status;
275 }
276 {
278
279 return sapi_voter_receive(&channel->impl.vital.voter, out_payload, max_size, &result, out_size);
280 }
281}
282
284{
285 if ((channel == NULL) || (!channel->is_open))
286 {
288 }
289
290 if (channel->type == SAPI_SAFECHANNEL_TYPE_DUAL_REDUNDANT)
291 {
292 switch (sapi_dual_channel_get_status(&channel->impl.dual))
293 {
299 default:
301 }
302 }
303 {
304 uint32_t healthy_count = 0U;
305
306 (void)sapi_voter_get_aggregated_health(&channel->impl.vital.voter,
307 &healthy_count, NULL);
308 if (healthy_count == 0U)
309 {
311 }
312 if (healthy_count < channel->link_count)
313 {
315 }
317 }
318}
319
321{
322 if (channel == NULL)
323 {
324 return SAPI_STATUS_OK;
325 }
326 if (!channel->is_open)
327 {
328 return SAPI_STATUS_OK;
329 }
330
331 if (channel->type == SAPI_SAFECHANNEL_TYPE_VITAL_VOTED)
332 {
333 uint32_t i;
334
335 for (i = 0U; i < channel->link_count; i++)
336 {
337 (void)sapi_channel_destroy(&channel->impl.vital.channels[i]);
338 }
339 (void)sapi_voter_destroy(&channel->impl.vital.voter);
340 }
341 safechannel_close_links(channel, channel->link_count);
342 channel->link_count = 0U;
343 channel->is_open = false;
344 return SAPI_STATUS_OK;
345}
sapi_status_t sapi_dual_channel_send(sapi_dual_channel_t *channel, const uint8_t *payload, uint8_t payload_size, uint32_t *out_ack_link_count)
Sends payload as a DATA frame on every configured redundant link - never gated by any negotiated stat...
sapi_dual_channel_status_t sapi_dual_channel_get_status(const sapi_dual_channel_t *channel)
Returns the aggregate connection status across every configured redundant link, as of the most recent...
sapi_status_t sapi_dual_channel_receive(sapi_dual_channel_t *channel, uint8_t *out_payload, uint8_t max_size, sapi_duration_ms_t timeout_ms, uint8_t *out_size)
Returns the most recently staged inbound DATA frame, actively polling the configured links (each auto...
sapi_status_t sapi_dual_channel_init(sapi_dual_channel_t *channel, const sapi_dual_channel_config_t *config)
Initializes a sapi_dual_channel_t: initializes every configured redundant link (sapi_dual_msgchannel_...
@ SAPI_DUAL_CHANNEL_STATUS_FULL
@ SAPI_DUAL_CHANNEL_STATUS_DOWN
@ SAPI_DUAL_CHANNEL_STATUS_DEGRADED
#define SAPI_SAFECHANNEL_MAX_LINKS
Maximum number of redundant endpoints one sapi_safechannel_t may be configured with,...
sapi_status_t sapi_safechannel_send(sapi_safechannel_t *channel, const uint8_t *payload, size_t payload_size)
Sends payload on the underlying channel - broadcast-with-ACK- wait for DUAL_REDUNDANT (sapi_dual_chan...
sapi_safechannel_link_status_t sapi_safechannel_get_status(const sapi_safechannel_t *channel)
Aggregate status across every configured endpoint - DOWN before the first send/receive.
sapi_status_t sapi_safechannel_open(sapi_safechannel_t *channel, const sapi_safechannel_config_t *config)
Opens a channel: opens every configured endpoint via the registered sapi_netlink backend,...
sapi_status_t sapi_safechannel_receive(sapi_safechannel_t *channel, uint8_t *out_payload, size_t max_size, sapi_duration_ms_t timeout_ms, size_t *out_size)
Returns the most recent inbound payload, actively polling the underlying channel if nothing was alrea...
sapi_safechannel_link_status_t
Aggregate link status, uniform across both wrapped types.
sapi_status_t sapi_safechannel_close(sapi_safechannel_t *channel)
Closes every endpoint this instance opened and releases the wrapped channel. Safe to call on an alrea...
@ SAPI_SAFECHANNEL_TYPE_VITAL_VOTED
@ SAPI_SAFECHANNEL_TYPE_DUAL_REDUNDANT
@ SAPI_SAFECHANNEL_LINK_DEGRADED
@ SAPI_SAFECHANNEL_LINK_DOWN
@ SAPI_SAFECHANNEL_LINK_FULL
sapi_status_t
Common result/status codes.
Definition sapi_status.h:27
@ SAPI_STATUS_INVALID_PARAM
Definition sapi_status.h:29
@ SAPI_STATUS_OK
Definition sapi_status.h:28
uint32_t sapi_duration_ms_t
Definition sapi_types.h:27
sapi_status_t sapi_voter_destroy(sapi_voter_t *voter)
Destroys a voter instance.
Definition sapi_voter.c:465
sapi_voting_result_t
Outcome of one sapi_voter_receive() call.
Definition sapi_voter.h:45
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_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
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
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_TIMEOUT
Definition sapi_voter.h:51
Checksum and CRC utilities for data integrity in redundant systems.
static sapi_status_t safechannel_open_one_endpoint(sapi_netlink_storage_t *storage, const sapi_safechannel_endpoint_t *endpoint, size_t message_size, sapi_duration_ms_t connect_timeout_ms, sapi_netlink_handle_t *out_handle)
Opens one endpoint, retrying sapi_netlink_open() with a short fixed delay until connect_timeout_ms el...
static sapi_status_t safechannel_vital_backend_recv(void *channel_handle, void *data, size_t data_size, uint32_t timeout_ms)
sapi_channel_config_t::recv bridge - see safechannel_vital_backend_send().
static void safechannel_close_links(sapi_safechannel_t *channel, uint32_t count)
Closes every already-opened link handles[0..count-1] and resets the caller's bookkeeping - used both ...
static sapi_status_t safechannel_vital_backend_send(void *channel_handle, const void *data, size_t data_size)
sapi_channel_config_t::send bridge: casts the opaque channel handle back to the sapi_netlink_handle_t...
Unified, transport-hiding channel factory (ADR-022).
Configuration for sapi_channel_init().
sapi_channel_send_fn send
sapi_channel_recv_fn recv
Configuration for sapi_dual_channel_init().
sapi_duration_ms_t ack_timeout_ms
sapi_dual_channel_status_callback_t status_callback
sapi_netlink_handle_t links[SAPI_DUAL_CHANNEL_MAX_LINKS]
Configuration for sapi_safechannel_open().
sapi_safechannel_type_t type
Configuration specific to SAPI_SAFECHANNEL_TYPE_DUAL_REDUNDANT.
sapi_duration_ms_t connect_timeout_ms
sapi_safechannel_endpoint_t endpoints[SAPI_SAFECHANNEL_MAX_LINKS]
sapi_dual_channel_status_callback_t status_callback
One remote endpoint to open internally - replaces a caller- supplied sapi_netlink_handle_t (ADR-022 s...
Caller-owned storage for one sapi_safechannel_t instance. Every field is private - reach it only thro...
sapi_netlink_storage_t links[SAPI_SAFECHANNEL_MAX_LINKS]
Configuration specific to SAPI_SAFECHANNEL_TYPE_VITAL_VOTED.
void(*) on_disagreement(void *context, sapi_voting_result_t result)
sapi_safechannel_endpoint_t endpoints[SAPI_SAFECHANNEL_MAX_LINKS]
sapi_voting_strategy_t voting_strategy
Vital channel message with integrated CRC-64.
Configuration for sapi_voter_init().
Definition sapi_voter.h:78
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