Skip to main content

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}