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_dual_channel.c
Go to the documentation of this file.
1
7
8#include <string.h>
9
11
17#define RTE_DUAL_CHANNEL_STALL_POLL_LIMIT 32U
18
26
27static rte_dual_channel_status_t dual_channel_compute_status(const rte_dual_channel_t *channel)
28{
29 uint32_t up_count = 0U;
30 uint32_t i;
31
32 for (i = 0U; i < channel->link_count; i++)
33 {
34 if (channel->link_up[i])
35 {
36 up_count++;
37 }
38 }
39
40 if (up_count == 0U)
41 {
43 }
44 if (up_count == channel->link_count)
45 {
47 }
49}
50
51static void dual_channel_update_status(rte_dual_channel_t *channel)
52{
53 rte_dual_channel_status_t new_status = dual_channel_compute_status(channel);
54
55 if (new_status != channel->last_status)
56 {
57 rte_dual_channel_status_t old_status = channel->last_status;
58
59 channel->last_status = new_status;
60 if (channel->status_callback != NULL)
61 {
62 channel->status_callback(new_status, old_status, channel->status_callback_ctx);
63 }
64 }
65}
66
74static rte_status_t dual_channel_poll_link_once(rte_dual_channel_t *channel, uint32_t link_index,
75 rte_duration_ms_t timeout_ms, dual_poll_result_t *out_result)
76{
78 uint8_t raw_size = 0U;
79 uint32_t sequence = 0U;
80 rte_status_t status;
82
83 out_result->matched = false;
84
85 status = rte_dual_msgchannel_receive(&channel->links[link_index], raw, (uint8_t)sizeof(raw), timeout_ms,
86 &raw_size, &sequence);
87 if (status != RTE_STATUS_OK)
88 {
89 return status;
90 }
91 if (raw_size < (uint8_t)sizeof(header))
92 {
93 /* Too short to even hold this layer's own header - treat as a
94 * defended-integrity failure, same family as a Layer-1 CRC/
95 * sequence failure, not a silently-ignored malformed frame. */
97 }
98 (void)memcpy(&header, raw, sizeof(header));
99
100 switch ((rte_dual_frame_kind_t)header.kind)
101 {
103 {
104 uint8_t app_size = (uint8_t)(raw_size - (uint8_t)sizeof(header));
106
107 if (app_size <= RTE_DUAL_CHANNEL_MAX_PAYLOAD)
108 {
109 (void)memcpy(channel->pending_data, &raw[sizeof(header)], app_size);
110 channel->pending_data_size = app_size;
111 channel->pending_data_valid = true;
112 }
113
114 /* Auto-ACK, best-effort/fire-and-forget: a lost ACK is
115 * observable to the sender as its own timeout on this link
116 * (ADR-020 section 2), not silently hidden here. */
117 ack.header.kind = (uint8_t)RTE_DUAL_FRAME_KIND_ACK;
118 ack.header.reserved[0] = 0U;
119 ack.header.reserved[1] = 0U;
120 ack.header.reserved[2] = 0U;
121 ack.acked_sequence = sequence;
122 (void)rte_dual_msgchannel_send(&channel->links[link_index], (const uint8_t *)&ack, (uint8_t)sizeof(ack),
123 channel->ack_timeout_ms, NULL);
124
125 out_result->matched = true;
126 out_result->kind = RTE_DUAL_FRAME_KIND_DATA;
127 break;
128 }
130 {
132
133 if (raw_size < (uint8_t)sizeof(ack))
134 {
136 }
137 (void)memcpy(&ack, raw, sizeof(ack));
138
139 out_result->matched = true;
140 out_result->kind = RTE_DUAL_FRAME_KIND_ACK;
141 out_result->ack_sequence = ack.acked_sequence;
142 break;
143 }
145 {
146 if (raw_size < (uint8_t)sizeof(rte_dual_state_frame_t))
147 {
149 }
150 (void)memcpy(&channel->pending_state, raw, sizeof(channel->pending_state));
151 channel->pending_state_valid = true;
152
153 out_result->matched = true;
154 out_result->kind = RTE_DUAL_FRAME_KIND_STATE;
155 break;
156 }
158 {
160
161 /* Auto-ACK, best-effort/fire-and-forget: acknowledges heartbeat */
162 ack.header.kind = (uint8_t)RTE_DUAL_FRAME_KIND_ACK;
163 ack.header.reserved[0] = 0U;
164 ack.header.reserved[1] = 0U;
165 ack.header.reserved[2] = 0U;
166 ack.acked_sequence = sequence;
167 (void)rte_dual_msgchannel_send(&channel->links[link_index], (const uint8_t *)&ack, (uint8_t)sizeof(ack),
168 channel->ack_timeout_ms, NULL);
169
170 out_result->matched = true;
172 break;
173 }
174 default:
175 /* Unrecognized kind - defensive only (not reachable through
176 * a conforming sender), ignore rather than fail. */
177 break;
178 }
179
180 return RTE_STATUS_OK;
181}
182
184{
185 uint32_t i;
186
187 if ((channel == NULL) || (config == NULL))
188 {
190 }
191 if ((config->link_count == 0U) || (config->link_count > RTE_DUAL_CHANNEL_MAX_LINKS))
192 {
194 }
195 for (i = 0U; i < config->link_count; i++)
196 {
197 if (config->links[i] == NULL)
198 {
200 }
201 }
202
203 channel->link_count = config->link_count;
204 for (i = 0U; i < config->link_count; i++)
205 {
207
208 msgcfg.link = config->links[i];
209 msgcfg.sender_id = config->sender_id;
210 msgcfg.expected_peer_id = config->expected_peer_id;
211 (void)rte_dual_msgchannel_init(&channel->links[i], &msgcfg);
212 channel->link_up[i] = false;
213 }
214 for (i = config->link_count; i < RTE_DUAL_CHANNEL_MAX_LINKS; i++)
215 {
216 channel->link_up[i] = false;
217 }
218
219 channel->ack_timeout_ms = config->ack_timeout_ms;
220 channel->status_callback = config->status_callback;
221 channel->status_callback_ctx = config->status_callback_ctx;
222 channel->last_status = RTE_DUAL_CHANNEL_STATUS_DOWN;
223
224 channel->pending_data_valid = false;
225 channel->pending_data_size = 0U;
226 channel->pending_state_valid = false;
227 (void)memset(&channel->pending_state, 0, sizeof(channel->pending_state));
228
229 return RTE_STATUS_OK;
230}
231
232/* rte_dual_channel_send()'s own per-link attempt, extracted: sends the frame on link index i,
233 * then (only if the send itself succeeded) polls that same link for its matching ACK. Every
234 * comment below on the stall-poll cap and the "no measurable time passed" allowance is unchanged
235 * from the loop body this replaces - REQ-DUAL-CHANNEL-007 still applies exactly as documented.
236 * *saw_hard_fault and *hard_fault_status are updated in place, once, the first time either the send
237 * or a poll reports RTE_STATUS_HARDWARE_FAULT across the WHOLE rte_dual_channel_send() call (not
238 * just this one link) - REQ-DUAL-CHANNEL-008, same "first, not last" choice as before this
239 * extraction. No logic changed. */
240static bool dual_channel_send_to_link_and_wait_ack(rte_dual_channel_t *channel, uint32_t i, const uint8_t *frame,
241 uint8_t frame_size, bool *saw_hard_fault,
242 rte_status_t *hard_fault_status)
243{
244 uint32_t sent_sequence = 0U;
245 rte_status_t send_status;
246 bool link_now_up = false;
247
248 send_status = rte_dual_msgchannel_send(&channel->links[i], frame, frame_size, channel->ack_timeout_ms,
249 &sent_sequence);
250 if ((send_status == RTE_STATUS_HARDWARE_FAULT) && (!*saw_hard_fault))
251 {
252 *saw_hard_fault = true;
253 *hard_fault_status = send_status;
254 }
255 if (send_status == RTE_STATUS_OK)
256 {
257 rte_duration_ms_t remaining = channel->ack_timeout_ms;
258 rte_timestamp_ms_t start_ms = 0U;
259 /* Bounds how many consecutive polls may complete without
260 * rte_timer_now() showing any measurable progress since
261 * start_ms, before this loop gives up on this link for this
262 * round - see the "else" branch below for why this can
263 * legitimately happen more than once and must not itself be
264 * unbounded. RTE_DUAL_CHANNEL_STALL_POLL_LIMIT is a fixed,
265 * generous cap (a real exchange needs at most a handful of
266 * iterations - one per DATA/STATE/foreign-ACK frame handled
267 * as a side effect before this link's own matching ACK
268 * arrives), not a tuned timing value. */
269 uint32_t stall_polls = 0U;
270
271 (void)rte_timer_now(&start_ms);
272
273 while ((remaining > 0U) && (stall_polls < RTE_DUAL_CHANNEL_STALL_POLL_LIMIT))
274 {
275 dual_poll_result_t result;
276 rte_status_t poll_status;
277 rte_timestamp_ms_t now_ms = 0U;
278
279 poll_status = dual_channel_poll_link_once(channel, i, remaining, &result);
280 if ((poll_status == RTE_STATUS_OK) && result.matched && (result.kind == RTE_DUAL_FRAME_KIND_ACK)
281 && (result.ack_sequence == sent_sequence))
282 {
283 link_now_up = true;
284 break;
285 }
286 if (poll_status == RTE_STATUS_HARDWARE_FAULT)
287 {
288 /* Genuinely broken (not just "no ACK frame arrived
289 * yet this poll"), e.g. HARDWARE_FAULT from the peer
290 * having closed/reset the connection - see this
291 * function's own header. No point continuing to poll
292 * a dead link for the rest of its own ack_timeout_ms
293 * budget. */
294 if (!*saw_hard_fault)
295 {
296 *saw_hard_fault = true;
297 *hard_fault_status = poll_status;
298 }
299 break;
300 }
301
302 /* Recompute the remaining budget from wall-clock elapsed
303 * time, not a fixed per-iteration decrement - a DATA/STATE
304 * frame handled as a side effect above may have consumed
305 * an arbitrary fraction of this link's own timeout
306 * already. */
307 (void)rte_timer_now(&now_ms);
308 if (now_ms > start_ms)
309 {
310 rte_timestamp_ms_t elapsed = now_ms - start_ms;
311
312 if (elapsed >= (rte_timestamp_ms_t)channel->ack_timeout_ms)
313 {
314 remaining = 0U;
315 }
316 else
317 {
318 remaining = channel->ack_timeout_ms - (rte_duration_ms_t)elapsed;
319 }
320 stall_polls = 0U;
321 }
322 else
323 {
324 /* REQ-DUAL-CHANNEL-007: now_ms == start_ms - no
325 * measurable time has passed on rte_timer_now()'s own
326 * tick resolution since this round started. This used
327 * to be treated as "no timer
328 * OSAdapter available, give up after one attempt"
329 * (REQ-OAL-LOG-001-style "never spin on an
330 * unmeasurable interval"), but a real localhost round
331 * trip (connect, send DATA, receive the peer's own
332 * DATA, auto-ACK it, receive the peer's own ACK)
333 * routinely completes inside a single millisecond
334 * tick, which made that same guard misfire as a false
335 * "no timer" abort after exactly one poll - discovered
336 * live via SITE's migration to rte_safechannel
337 * (ADR-022), where both sites send their negotiation
338 * beacon at nearly the same instant over a real
339 * loopback TCP link. `remaining` is left unchanged so
340 * a fast exchange like that one gets the extra polls
341 * it needs; stall_polls bounds how many such
342 * no-progress iterations are allowed before this loop
343 * gives up anyway, so a link with a genuinely
344 * non-advancing or absent timer still cannot spin
345 * forever. */
346 stall_polls++;
347 }
348 }
349 }
350 return link_now_up;
351}
352
353rte_status_t rte_dual_channel_send(rte_dual_channel_t *channel, const uint8_t *payload, uint8_t payload_size,
354 uint32_t *out_ack_link_count)
355{
356 uint8_t frame[(size_t)RTE_DUAL_CHANNEL_MAX_PAYLOAD + sizeof(rte_dual_frame_header_t)];
357 uint8_t frame_size;
358 uint32_t i;
359 uint32_t ack_count = 0U;
360 /* REQ-DUAL-CHANNEL-008: a link whose own send/receive reports
361 * something other than RTE_STATUS_OK/RTE_STATUS_TIMEOUT (e.g.
362 * RTE_STATUS_HARDWARE_FAULT from a closed/reset connection) is
363 * genuinely broken, not just quiet - see this function's own header
364 * for why collapsing that into the same generic TIMEOUT every other
365 * "no ACK yet" case returns hid real transport failures from every
366 * caller. Kept as the FIRST such status seen across all links this
367 * call, not the last - an arbitrary but stable choice among possibly
368 * several simultaneous failures. */
369 bool saw_hard_fault = false;
370 rte_status_t hard_fault_status = RTE_STATUS_OK;
371
372 if (channel == NULL)
373 {
375 }
376 if ((payload == NULL) && (payload_size != 0U))
377 {
379 }
380 if (payload_size > RTE_DUAL_CHANNEL_MAX_PAYLOAD)
381 {
383 }
384
385 {
387
388 header.kind = (uint8_t)RTE_DUAL_FRAME_KIND_DATA;
389 header.reserved[0] = 0U;
390 header.reserved[1] = 0U;
391 header.reserved[2] = 0U;
392 (void)memcpy(frame, &header, sizeof(header));
393 if (payload_size > 0U)
394 {
395 (void)memcpy(&frame[sizeof(header)], payload, payload_size);
396 }
397 frame_size = (uint8_t)(sizeof(header) + payload_size);
398 }
399
400 for (i = 0U; i < channel->link_count; i++)
401 {
402 bool link_now_up = dual_channel_send_to_link_and_wait_ack(channel, i, frame, frame_size, &saw_hard_fault,
403 &hard_fault_status);
404
405 channel->link_up[i] = link_now_up;
406 if (link_now_up)
407 {
408 ack_count++;
409 }
410 }
411
412 if (out_ack_link_count != NULL)
413 {
414 *out_ack_link_count = ack_count;
415 }
416
417 dual_channel_update_status(channel);
418
419 if (ack_count > 0U)
420 {
421 return RTE_STATUS_OK;
422 }
423 return saw_hard_fault ? hard_fault_status : RTE_STATUS_TIMEOUT;
424}
425
426rte_status_t rte_dual_channel_receive(rte_dual_channel_t *channel, uint8_t *out_payload, uint8_t max_size,
427 rte_duration_ms_t timeout_ms, uint8_t *out_size)
428{
429 if ((channel == NULL) || (out_payload == NULL) || (out_size == NULL) || (max_size == 0U))
430 {
432 }
433
434 {
435 bool saw_hard_fault = false;
436 rte_status_t hard_fault_status = RTE_STATUS_OK;
437
438 if (!channel->pending_data_valid)
439 {
440 uint32_t i;
441 rte_duration_ms_t per_link_timeout = (channel->link_count > 0U) ? (timeout_ms / channel->link_count) : 0U;
442
443 /* Sweeps every configured link every call, even after an
444 * earlier link in this same sweep already staged a DATA frame -
445 * stopping early here would let a link with a shorter path (or
446 * one that always has traffic) starve every other redundant
447 * link of its own auto-ACK indefinitely (ADR-020 section 2: a
448 * redundant link should degrade to DOWN only from genuinely not
449 * responding, never from this module never getting around to
450 * polling it). Still swept to completion even after a hard
451 * fault on an earlier link, for the same reason. */
452 for (i = 0U; i < channel->link_count; i++)
453 {
454 dual_poll_result_t result;
455 rte_status_t poll_status;
456
457 poll_status = dual_channel_poll_link_once(channel, i, per_link_timeout, &result);
458 /* REQ-DUAL-CHANNEL-008: see rte_dual_channel_send()'s own
459 * doc on this same distinction - a link reporting
460 * something other than OK/TIMEOUT (e.g. HARDWARE_FAULT
461 * from a closed/reset connection) is broken, not just
462 * quiet, and that must not be silently collapsed into the
463 * same generic TIMEOUT "nothing staged yet" returns below. */
464 if ((poll_status == RTE_STATUS_HARDWARE_FAULT) && (!saw_hard_fault))
465 {
466 saw_hard_fault = true;
467 hard_fault_status = poll_status;
468 }
469 }
470 }
471
472 if (!channel->pending_data_valid)
473 {
474 return saw_hard_fault ? hard_fault_status : RTE_STATUS_TIMEOUT;
475 }
476 }
477 if (channel->pending_data_size > max_size)
478 {
479 /* Caller's buffer is too small for a staged frame - reported,
480 * not silently truncated (safety-relevant data). */
482 }
483
484 (void)memcpy(out_payload, channel->pending_data, channel->pending_data_size);
485 *out_size = channel->pending_data_size;
486 channel->pending_data_valid = false;
487
488 return RTE_STATUS_OK;
489}
490
492{
494 uint32_t i;
495 uint32_t ack_count = 0U;
496 bool saw_hard_fault = false;
497 rte_status_t hard_fault_status = RTE_STATUS_OK;
498 rte_timestamp_ms_t now_ms = 0U;
499
500 if (channel == NULL)
501 {
503 }
504
505 /* rte_dual_heartbeat_frame_t has a 4-byte compiler-inserted
506 * alignment gap between `header` (4 bytes) and the 8-byte-aligned
507 * `timestamp_ms` that follows it - unlike every other frame type in
508 * this file, whose fields happen to add up to an already-aligned
509 * offset. Without this memset, that gap is indeterminate stack
510 * content that still gets read (and checksummed/transmitted) by the
511 * rte_dual_msgchannel_send() call below, since it hashes/sends
512 * `sizeof(frame)` raw bytes, not just the named fields - found via
513 * Valgrind (RTE_ENABLE_ASAN/Valgrind CTest memcheck target)
514 * flagging a real "use of uninitialised value" inside
515 * rte_checksum_crc64(), reached from here. */
516 (void)memset(&frame, 0, sizeof(frame));
517 (void)rte_timer_now(&now_ms);
518 frame.header.kind = (uint8_t)RTE_DUAL_FRAME_KIND_HEARTBEAT;
519 frame.header.reserved[0] = 0U;
520 frame.header.reserved[1] = 0U;
521 frame.header.reserved[2] = 0U;
522 frame.timestamp_ms = (uint64_t)now_ms;
523
524 for (i = 0U; i < channel->link_count; i++)
525 {
526 bool link_now_up = dual_channel_send_to_link_and_wait_ack(
527 channel, i, (const uint8_t *)&frame, (uint8_t)sizeof(frame), &saw_hard_fault, &hard_fault_status);
528
529 channel->link_up[i] = link_now_up;
530 if (link_now_up)
531 {
532 ack_count++;
533 }
534 }
535
536 if (out_ack_link_count != NULL)
537 {
538 *out_ack_link_count = ack_count;
539 }
540
541 dual_channel_update_status(channel);
542
543 if (ack_count > 0U)
544 {
545 return RTE_STATUS_OK;
546 }
547 return saw_hard_fault ? hard_fault_status : RTE_STATUS_TIMEOUT;
548}
549
551 bool channel_degraded, uint64_t timestamp_ms)
552{
554 uint32_t i;
555 uint32_t sent_count = 0U;
556
557 if (channel == NULL)
558 {
560 }
561
562 frame.header.kind = (uint8_t)RTE_DUAL_FRAME_KIND_STATE;
563 frame.header.reserved[0] = 0U;
564 frame.header.reserved[1] = 0U;
565 frame.header.reserved[2] = 0U;
566 frame.state = (uint8_t)state;
567 frame.channel_degraded = channel_degraded ? 1U : 0U;
568 frame.reserved = 0U;
569 frame.timestamp_ms = timestamp_ms;
570
571 for (i = 0U; i < channel->link_count; i++)
572 {
573 rte_status_t status = rte_dual_msgchannel_send(&channel->links[i], (const uint8_t *)&frame,
574 (uint8_t)sizeof(frame), channel->ack_timeout_ms, NULL);
575 if (status == RTE_STATUS_OK)
576 {
577 sent_count++;
578 }
579 }
580
581 return (sent_count > 0U) ? RTE_STATUS_OK : RTE_STATUS_TIMEOUT;
582}
583
585 rte_dual_state_frame_t *out_frame)
586{
587 if ((channel == NULL) || (out_frame == NULL))
588 {
590 }
591
592 if (!channel->pending_state_valid)
593 {
594 uint32_t i;
595 rte_duration_ms_t per_link_timeout = (channel->link_count > 0U) ? (timeout_ms / channel->link_count) : 0U;
596
597 /* Sweeps every link every call - see rte_dual_channel_receive()'s
598 * own comment on why stopping early would starve other links. */
599 for (i = 0U; i < channel->link_count; i++)
600 {
601 dual_poll_result_t result;
602
603 (void)dual_channel_poll_link_once(channel, i, per_link_timeout, &result);
604 }
605 }
606
607 if (!channel->pending_state_valid)
608 {
609 return RTE_STATUS_TIMEOUT;
610 }
611
612 *out_frame = channel->pending_state;
613 channel->pending_state_valid = false;
614
615 return RTE_STATUS_OK;
616}
617
619{
620 if (channel == NULL)
621 {
623 }
624 return channel->last_status;
625}
626
627bool rte_dual_channel_is_link_up(const rte_dual_channel_t *channel, uint32_t link_index)
628{
629 if ((channel == NULL) || (link_index >= channel->link_count))
630 {
631 return false;
632 }
633 return channel->link_up[link_index];
634}
635
637{
638 uint32_t i;
639
640 if (channel == NULL)
641 {
643 }
644 for (i = 0U; i < RTE_DUAL_CHANNEL_MAX_LINKS; i++)
645 {
646 (void)rte_dual_msgchannel_set_resync_on_sequence_error(&channel->links[i], enable);
647 }
648 return RTE_STATUS_OK;
649}
bool rte_dual_channel_is_link_up(const rte_dual_channel_t *channel, uint32_t link_index)
Returns whether one specific configured link is currently considered up, as of the most recent rte_du...
rte_status_t rte_dual_msgchannel_receive(rte_dual_msgchannel_t *channel, uint8_t *out_payload, uint8_t payload_max_size, rte_duration_ms_t timeout_ms, uint8_t *out_payload_size, uint32_t *out_sequence)
Receives one frame over config->link, blocking at most timeout_ms, verifies its CRC-64 and sequence c...
rte_status_t rte_dual_channel_receive_state_frame(rte_dual_channel_t *channel, rte_duration_ms_t timeout_ms, rte_dual_state_frame_t *out_frame)
Returns the most recently staged inbound STATE frame, actively polling the configured links if none w...
rte_status_t rte_dual_channel_send(rte_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...
rte_status_t rte_dual_channel_init(rte_dual_channel_t *channel, const rte_dual_channel_config_t *config)
Initializes a rte_dual_channel_t: initializes every configured redundant link (rte_dual_msgchannel_in...
rte_dual_channel_status_t
Aggregate connection status of a rte_dual_channel_t across all of its configured redundant links (ADR...
rte_status_t rte_dual_channel_send_heartbeat(rte_dual_channel_t *channel, uint32_t *out_ack_count)
Transmits a connection maintenance heartbeat frame across every configured link and waits up to ack_t...
rte_dual_state_t
State of one instance in a dual (two-redundant-instance) relationship, as decided by rte_dual_negotia...
#define RTE_DUAL_CHANNEL_MAX_PAYLOAD
Max application payload bytes usable via rte_dual_channel_send()/_receive() - Layer-1's own RTE_DUAL_...
#define RTE_DUAL_CHANNEL_MAX_LINKS
Maximum number of redundant links one rte_dual_channel_t may be configured with. Fixed,...
rte_dual_channel_status_t rte_dual_channel_get_status(const rte_dual_channel_t *channel)
Returns the aggregate connection status across every configured redundant link, as of the most recent...
rte_status_t rte_dual_channel_receive(rte_dual_channel_t *channel, uint8_t *out_payload, uint8_t max_size, rte_duration_ms_t timeout_ms, uint8_t *out_size)
Returns the most recently staged inbound DATA frame, actively polling the configured links (each auto...
rte_dual_frame_kind_t
Which kind of Layer-2 frame a given Layer-1 payload holds. Stored as the first byte of every rte_dual...
rte_status_t rte_dual_msgchannel_init(rte_dual_msgchannel_t *channel, const rte_dual_msgchannel_config_t *config)
Initializes a rte_dual_msgchannel_t: starts both sequence counters at 0.
rte_status_t rte_dual_channel_set_resync_on_sequence_error(rte_dual_channel_t *channel, bool enable)
#define RTE_DUAL_MSGCHANNEL_MAX_PAYLOAD
Max payload bytes usable via rte_dual_msgchannel_send()/ _receive() - bounded by rte_vital_message_t'...
rte_status_t rte_dual_msgchannel_set_resync_on_sequence_error(rte_dual_msgchannel_t *channel, bool enable)
Turns sequence resynchronisation on or off (default off after init).
rte_status_t rte_dual_msgchannel_send(rte_dual_msgchannel_t *channel, const uint8_t *payload, uint8_t payload_size, rte_duration_ms_t timeout_ms, uint32_t *out_sequence)
Wraps payload in a rte_vital_message_t (this channel's own sender_id and next sequence_number) and se...
rte_status_t rte_dual_channel_send_state_frame(rte_dual_channel_t *channel, rte_dual_state_t state, bool channel_degraded, uint64_t timestamp_ms)
Sends a rte_dual_state_frame_t (rte_dual_frames.h) on every configured redundant link - fire-and-forg...
@ RTE_DUAL_CHANNEL_STATUS_FULL
@ RTE_DUAL_CHANNEL_STATUS_DOWN
@ RTE_DUAL_CHANNEL_STATUS_DEGRADED
@ RTE_DUAL_FRAME_KIND_STATE
@ RTE_DUAL_FRAME_KIND_HEARTBEAT
@ RTE_DUAL_FRAME_KIND_ACK
@ RTE_DUAL_FRAME_KIND_DATA
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_DATA_CORRUPTION
Definition rte_status.h:37
@ RTE_STATUS_HARDWARE_FAULT
Definition rte_status.h:36
@ RTE_STATUS_TIMEOUT
Definition rte_status.h:32
rte_status_t rte_timer_now(rte_timestamp_ms_t *out_now_ms)
Returns the current monotonic time base used by all timers.
Definition rte_timer.c:139
uint32_t rte_duration_ms_t
Definition rte_types.h:27
uint64_t rte_timestamp_ms_t
Definition rte_types.h:30
static rte_status_t dual_channel_poll_link_once(rte_dual_channel_t *channel, uint32_t link_index, rte_duration_ms_t timeout_ms, dual_poll_result_t *out_result)
One receive attempt on one link, dispatched by frame kind: DATA is auto-ACKed and staged for rte_dual...
#define RTE_DUAL_CHANNEL_STALL_POLL_LIMIT
Cap on consecutive rte_dual_channel_send() ACK-wait polls that may complete without rte_timer_now() s...
"DualChannel" layer of ADR-020: wraps 1..N redundant rte_dual_msgchannel_t links for fault-tolerant,...
OS Abstraction Layer - Timer service.
Result of one dual_channel_poll_link_once() attempt.
rte_dual_frame_kind_t kind
RTE_DUAL_FRAME_KIND_ACK payload.
Configuration for rte_dual_channel_init().
rte_dual_channel_status_callback_t status_callback
rte_duration_ms_t ack_timeout_ms
rte_netlink_handle_t links[RTE_DUAL_CHANNEL_MAX_LINKS]
One rte_dual_channel_t instance's state. Caller-owned storage; every field is private - reach it only...
Common 4-byte header prefixing every Layer-2 frame; kept a fixed 4 bytes (not just 1) so the fields t...
RTE_DUAL_FRAME_KIND_HEARTBEAT payload - connection maintenance heartbeat.
Configuration for rte_dual_msgchannel_init().
RTE_DUAL_FRAME_KIND_STATE payload - rte_dual_negotiator_t's own periodic beacon.