leodos_protocols/transport/srspp/dtn.rs
1/// Delay-tolerant delivery for SRSPP.
2///
3/// When the SRSPP sender detects that a destination is
4/// unreachable (e.g., no ground station in LOS), the whole
5/// message is written to a [`MessageStore`] instead of entering
6/// the SRSPP retransmit buffer. The driver periodically
7/// checks reachability via [`Reachable`] and drains stored
8/// messages through the normal SRSPP path when contact
9/// returns.
10use crate::network::isl::address::Address;
11
12// ── Store ───────────────────────────────────────────────
13
14/// Result of a [`MessageStore::write`] call.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum StoreResult {
17 /// Message was stored successfully.
18 Stored,
19 /// Store is full — message was not stored.
20 Full,
21}
22
23/// Persistent message store for delay-tolerant delivery.
24///
25/// Stores whole application messages (pre-segmentation) on
26/// behalf of the SRSPP sender. Messages are keyed by
27/// destination [`Address`] and drained in FIFO order per
28/// target.
29///
30/// Implementations typically use OSAL file I/O
31/// (`leodos_libcfs::os::fs`) for persistence. A RAM-backed
32/// implementation can be used for testing.
33pub trait MessageStore {
34 /// Persist a message for later delivery.
35 ///
36 /// - `target` — destination address (e.g., a ground
37 /// station).
38 /// - `data` — raw message bytes (pre-segmentation).
39 /// - `ttl_secs` — time-to-live in seconds; 0 = no
40 /// expiry.
41 /// - `created_at_secs` — creation timestamp in seconds
42 /// (wrapping).
43 fn write(
44 &mut self,
45 target: Address,
46 data: &[u8],
47 ttl_secs: u16,
48 created_at_secs: u32,
49 ) -> StoreResult;
50
51 /// Read and remove the oldest stored message for
52 /// `target`. Copies the message into `buf` and returns
53 /// its length. Returns `None` if nothing is stored for
54 /// that target.
55 fn read(&mut self, target: Address, buf: &mut [u8]) -> Option<usize>;
56
57 /// Returns the byte length of the next message for
58 /// `target` without removing it. Used by the driver to
59 /// check if the message fits in the SRSPP buffer before
60 /// reading.
61 fn peek_size(&self, target: Address) -> Option<usize>;
62
63 /// Bitmap of targets that have pending messages.
64 /// Bit N set = ground station N has at least one
65 /// stored message.
66 fn pending_targets(&self) -> u16;
67
68 /// Discard all messages whose TTL has expired.
69 /// `now_secs` uses the same epoch as `created_at_secs`.
70 fn expire(&mut self, now_secs: u32);
71}
72
73// ── Reachable ───────────────────────────────────────────
74
75/// Reachability oracle for delay-tolerant delivery.
76///
77/// The SRSPP sender queries this before transmitting. If
78/// the target is unreachable from the origin, the message
79/// goes to the [`MessageStore`] instead of the retransmit buffer.
80pub trait Reachable {
81 /// Returns `true` if `target` is reachable from
82 /// `origin` at this moment.
83 fn is_reachable(&self, origin: Address, target: Address) -> bool;
84}
85
86// ── Discard ─────────────────────────────────────────────
87
88/// Reason a stored message was discarded.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum DiscardReason {
91 /// TTL expired before the message could be sent.
92 Expired,
93 /// The message won't survive until the next contact
94 /// window.
95 WontSurviveUntilContact,
96 /// The store is full and the message could not be
97 /// stored.
98 StoreFull,
99}
100
101/// Callback invoked when a stored message is discarded.
102///
103/// The default [`SilentDiscard`] does nothing. Apps can
104/// implement this to log, count, or persist discards.
105pub trait DiscardPolicy {
106 /// Called when a message is discarded.
107 fn on_discard(&mut self, target: Address, data: &[u8], reason: DiscardReason);
108}
109
110// ── Default implementations ─────────────────────────────
111
112/// A no-op store that rejects all writes. Used when DTN
113/// is disabled (paired with [`AlwaysReachable`]).
114pub struct NoStore;
115
116impl MessageStore for NoStore {
117 fn write(
118 &mut self,
119 _target: Address,
120 _data: &[u8],
121 _ttl_secs: u16,
122 _created_at_secs: u32,
123 ) -> StoreResult {
124 StoreResult::Full
125 }
126
127 fn read(&mut self, _target: Address, _buf: &mut [u8]) -> Option<usize> {
128 None
129 }
130
131 fn peek_size(&self, _target: Address) -> Option<usize> {
132 None
133 }
134
135 fn pending_targets(&self) -> u16 {
136 0
137 }
138
139 fn expire(&mut self, _now_secs: u32) {}
140}
141
142/// An oracle that considers all destinations reachable.
143/// Used when DTN is disabled.
144pub struct AlwaysReachable;
145
146impl Reachable for AlwaysReachable {
147 fn is_reachable(&self, _origin: Address, _target: Address) -> bool {
148 true
149 }
150}
151
152/// A discard policy that silently drops messages.
153pub struct SilentDiscard;
154
155impl DiscardPolicy for SilentDiscard {
156 fn on_discard(&mut self, _target: Address, _data: &[u8], _reason: DiscardReason) {}
157}