Skip to main content

leodos_protocols/datalink/reliability/
mod.rs

1//! Hop-by-hop reliable frame delivery for the datalink layer.
2//!
3//! Provides the [`ReliabilityWrite`] / [`ReliabilityRead`]
4//! traits, COP-1 wrappers ([`Cop1Writer`], [`Cop1Reader`]), and
5//! [`NoReliability`] as a passthrough.
6
7use cop1::farm::{FarmActions, FarmConfig, FarmEvent, FarmMachine};
8use cop1::fop::{FopActions, FopConfig, FopEvent, FopMachine};
9
10use crate::datalink::framing::sdlp::tc::{BypassFlag, ControlFlag};
11
12/// COP-1 (CCSDS 232.1-B-2) state machines.
13pub mod cop1;
14
15/// COP-1 sender (FOP-1) state machine interface.
16pub trait ReliabilityWrite {
17    /// Action to take after processing a frame.
18    type Action;
19    /// Processes an outgoing frame through the reliability layer.
20    fn write(&mut self, frame: &[u8]) -> Self::Action;
21}
22
23/// COP-1 receiver (FARM-1) state machine interface.
24pub trait ReliabilityRead {
25    /// Action to take after processing a frame.
26    type Action;
27    /// Processes an incoming frame through the reliability layer.
28    fn read(&mut self, frame: &[u8]) -> Self::Action;
29}
30
31/// COP-1 sender-side reliability (FOP-1).
32///
33/// Wraps a [`FopMachine`] and implements [`ReliabilityWrite`].
34/// Each [`write`](ReliabilityWrite::write) call feeds the data
35/// as a Type-AD (sequence-controlled) FDU. For Type-BD frames or
36/// management directives, use [`fop_mut`](Self::fop_mut).
37pub struct Cop1Writer<const WIN: usize, const BUF: usize> {
38    fop: FopMachine<WIN, BUF>,
39}
40
41impl<const WIN: usize, const BUF: usize> Cop1Writer<WIN, BUF> {
42    /// Creates a new COP-1 writer in the Initial state.
43    pub fn new(config: FopConfig) -> Self {
44        Self {
45            fop: FopMachine::new(config),
46        }
47    }
48
49    /// Returns a reference to the inner FOP-1 state machine.
50    pub fn fop(&self) -> &FopMachine<WIN, BUF> {
51        &self.fop
52    }
53
54    /// Returns a mutable reference for direct FOP-1 control.
55    pub fn fop_mut(&mut self) -> &mut FopMachine<WIN, BUF> {
56        &mut self.fop
57    }
58}
59
60impl<const WIN: usize, const BUF: usize> ReliabilityWrite
61    for Cop1Writer<WIN, BUF>
62{
63    type Action = FopActions;
64
65    fn write(&mut self, frame: &[u8]) -> FopActions {
66        let mut actions = FopActions::new();
67        self.fop
68            .handle(FopEvent::SendAd { fdu: frame }, &mut actions);
69        actions
70    }
71}
72
73/// Result of processing a frame through COP-1 FARM-1.
74#[derive(Debug, Copy, Clone, Eq, PartialEq)]
75pub enum Cop1ReadResult {
76    /// The frame was accepted for delivery.
77    Accept,
78    /// The frame was discarded (out of sequence, lockout, etc).
79    Discard,
80}
81
82/// COP-1 receiver-side reliability (FARM-1).
83///
84/// Wraps a [`FarmMachine`] and implements [`ReliabilityRead`].
85/// Each [`read`](ReliabilityRead::read) call parses the TC
86/// frame header to classify it as AD or BD and feeds the FARM-1
87/// state machine. BC (control) frames are treated as invalid in
88/// the trait impl — use [`farm_mut`](Self::farm_mut) for direct
89/// BC handling.
90pub struct Cop1Reader {
91    farm: FarmMachine,
92}
93
94impl Cop1Reader {
95    /// Creates a new COP-1 reader in the Open state.
96    pub fn new(config: FarmConfig) -> Self {
97        Self {
98            farm: FarmMachine::new(config),
99        }
100    }
101
102    /// Returns a reference to the inner FARM-1 state machine.
103    pub fn farm(&self) -> &FarmMachine {
104        &self.farm
105    }
106
107    /// Returns a mutable reference for direct FARM-1 control.
108    pub fn farm_mut(&mut self) -> &mut FarmMachine {
109        &mut self.farm
110    }
111}
112
113impl ReliabilityRead for Cop1Reader {
114    type Action = Cop1ReadResult;
115
116    fn read(&mut self, frame: &[u8]) -> Cop1ReadResult {
117        use cop1::farm::FarmAction;
118        use crate::datalink::framing::sdlp::tc::TelecommandTransferFrame;
119
120        let mut actions = FarmActions::new();
121
122        let Ok(tc_frame) = TelecommandTransferFrame::parse(frame)
123        else {
124            self.farm
125                .handle(FarmEvent::InvalidFrame, &mut actions);
126            return Cop1ReadResult::Discard;
127        };
128
129        let bypass = tc_frame.header().bypass_flag();
130        let control = tc_frame.header().control_flag();
131        let seq = tc_frame.header().sequence_num();
132        let fdu = tc_frame.data_field();
133
134        match (bypass, control) {
135            (BypassFlag::TypeA, ControlFlag::TypeD) => {
136                // AD frame: sequence-controlled data
137                self.farm.handle(
138                    FarmEvent::AdFrame {
139                        seq,
140                        buffer_available: true,
141                        fdu,
142                    },
143                    &mut actions,
144                );
145            }
146            (BypassFlag::TypeB, ControlFlag::TypeD) => {
147                // BD frame: bypass data
148                self.farm.handle(
149                    FarmEvent::BdFrame { fdu },
150                    &mut actions,
151                );
152            }
153            _ => {
154                // BC (TypeB+TypeC) or invalid (TypeA+TypeC):
155                // use farm_mut() for direct BC handling.
156                self.farm
157                    .handle(FarmEvent::InvalidFrame, &mut actions);
158                return Cop1ReadResult::Discard;
159            }
160        }
161
162        for action in &actions {
163            if matches!(action, FarmAction::Accept { .. }) {
164                return Cop1ReadResult::Accept;
165            }
166        }
167
168        Cop1ReadResult::Discard
169    }
170}
171
172/// No-op reliability layer (passthrough).
173///
174/// Accepts all frames without sequencing or retransmission.
175pub struct NoReliability;
176
177impl ReliabilityWrite for NoReliability {
178    type Action = ();
179
180    fn write(&mut self, _frame: &[u8]) {}
181}
182
183impl ReliabilityRead for NoReliability {
184    type Action = ();
185
186    fn read(&mut self, _frame: &[u8]) {}
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use crate::ids::{Scid, Vcid};
193    use cop1::fop::{FopAction, FopState};
194
195    fn fop_config() -> FopConfig {
196        FopConfig::builder()
197            .window_width(4)
198            .transmission_limit(3)
199            .timeout_type(0)
200            .build()
201    }
202
203    fn farm_config() -> FarmConfig {
204        FarmConfig::builder().vcid(Vcid::new(0)).window_width(10).build()
205    }
206
207    #[test]
208    fn cop1_writer_rejects_in_initial_state() {
209        let mut writer: Cop1Writer<8, 1024> =
210            Cop1Writer::new(fop_config());
211
212        let actions = writer.write(&[1, 2, 3]);
213        assert!(actions.iter().any(|a| matches!(a, FopAction::Reject)));
214    }
215
216    #[test]
217    fn cop1_writer_sends_ad_when_active() {
218        let mut writer: Cop1Writer<8, 1024> =
219            Cop1Writer::new(fop_config());
220
221        let mut init_actions = FopActions::new();
222        writer
223            .fop_mut()
224            .handle(FopEvent::InitAdNoClcw, &mut init_actions);
225        assert_eq!(writer.fop().state(), FopState::Active);
226
227        let actions = writer.write(&[0xAA, 0xBB]);
228        assert!(actions
229            .iter()
230            .any(|a| matches!(a, FopAction::TransmitAd { seq: 0 })));
231    }
232
233    #[test]
234    fn cop1_reader_accepts_in_sequence_ad() {
235        let mut reader = Cop1Reader::new(farm_config());
236
237        let mut buf = [0u8; 64];
238        let frame =
239            crate::datalink::framing::sdlp::tc::TelecommandTransferFrame::builder()
240                .buffer(&mut buf)
241                .scid(Scid::new(1))
242                .vcid(Vcid::new(0))
243                .bypass_flag(BypassFlag::TypeA)
244                .control_flag(ControlFlag::TypeD)
245                .seq(0)
246                .data_field_len(4)
247                .build()
248                .unwrap();
249        frame.data_field_mut().copy_from_slice(&[1, 2, 3, 4]);
250        let frame_len = frame.frame_len();
251
252        let result = reader.read(&buf[..frame_len]);
253        assert_eq!(result, Cop1ReadResult::Accept);
254        assert_eq!(reader.farm().vr(), 1);
255    }
256
257    #[test]
258    fn cop1_reader_accepts_bd_frame() {
259        let mut reader = Cop1Reader::new(farm_config());
260
261        let mut buf = [0u8; 64];
262        let frame =
263            crate::datalink::framing::sdlp::tc::TelecommandTransferFrame::builder()
264                .buffer(&mut buf)
265                .scid(Scid::new(1))
266                .vcid(Vcid::new(0))
267                .bypass_flag(BypassFlag::TypeB)
268                .control_flag(ControlFlag::TypeD)
269                .seq(0)
270                .data_field_len(2)
271                .build()
272                .unwrap();
273        frame.data_field_mut().copy_from_slice(&[0xAA, 0xBB]);
274        let frame_len = frame.frame_len();
275
276        let result = reader.read(&buf[..frame_len]);
277        assert_eq!(result, Cop1ReadResult::Accept);
278    }
279
280    #[test]
281    fn no_reliability_passthrough() {
282        let mut writer = NoReliability;
283        let mut reader = NoReliability;
284
285        writer.write(&[1, 2, 3]);
286        reader.read(&[1, 2, 3]);
287    }
288}