Skip to main content

leodos_protocols/datalink/reliability/cop1/
farm.rs

1//! FARM-1 (Frame Acceptance and Reporting Mechanism).
2//!
3//! CCSDS 232.1-B-2 Section 6. Receiver-side state machine for COP-1.
4//! Processes incoming TC transfer frames and generates CLCW reports.
5//!
6//! Three states: Open (S1), Wait (S2), Lockout (S3).
7//! Frame types determined by Bypass and Control Command flags:
8//! - AD: Bypass=0, CC=0 — sequence-controlled data
9//! - BC: Bypass=1, CC=1 — control commands (Unlock, Set V(R))
10//! - BD: Bypass=1, CC=0 — expedited/bypass data
11
12use heapless::Vec;
13
14use crate::ids::Vcid;
15use super::clcw::Clcw;
16
17/// Maximum actions per event.
18const MAX_ACTIONS: usize = 4;
19
20/// FARM-1 states per CCSDS 232.1-B-2 Table 6-1.
21#[derive(Debug, Copy, Clone, Eq, PartialEq)]
22pub enum FarmState {
23    /// S1: normal operation, accepting in-sequence AD frames.
24    Open,
25    /// S2: no buffer space, Wait flag is set.
26    Wait,
27    /// S3: lockout due to out-of-window frame.
28    Lockout,
29}
30
31/// BC control commands carried in Type-BC frames.
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33pub enum ControlCommand {
34    /// Reset FARM-1 to Open state, clear all flags.
35    Unlock,
36    /// Set V(R) to the specified value.
37    SetVr(u8),
38}
39
40/// Events that drive the FARM-1 state machine.
41#[derive(Debug, Clone)]
42pub enum FarmEvent<'a> {
43    /// A valid Type-AD transfer frame arrived.
44    AdFrame {
45        /// Frame Sequence Number N(S).
46        seq: u8,
47        /// Whether a buffer is available for this frame.
48        buffer_available: bool,
49        /// Frame Data Unit payload.
50        fdu: &'a [u8],
51    },
52
53    /// A valid Type-BD transfer frame arrived.
54    BdFrame {
55        /// Frame Data Unit payload.
56        fdu: &'a [u8],
57    },
58
59    /// A valid Type-BC transfer frame with a control command.
60    BcFrame {
61        /// The control command.
62        command: ControlCommand,
63    },
64
65    /// An invalid frame arrived (failed validation).
66    InvalidFrame,
67
68    /// Buffer space became available (flow control release).
69    BufferRelease,
70
71    /// Time to generate a CLCW report.
72    ClcwReport,
73}
74
75/// Actions that FARM-1 requests the driver to perform.
76#[derive(Debug, Clone)]
77pub enum FarmAction<'a> {
78    /// Accept an FDU and deliver it to higher procedures.
79    Accept {
80        /// The FDU payload to deliver.
81        fdu: &'a [u8],
82    },
83
84    /// Discard the received frame (no delivery).
85    Discard,
86
87    /// A CLCW report is ready; read it from [`FarmMachine::clcw`].
88    ClcwReady,
89}
90
91/// Collection of actions emitted by FARM-1.
92#[derive(Debug)]
93pub struct FarmActions<'a> {
94    inner: Vec<FarmAction<'a>, MAX_ACTIONS>,
95}
96
97impl<'a> FarmActions<'a> {
98    /// Create a new empty actions collection.
99    pub fn new() -> Self {
100        Self { inner: Vec::new() }
101    }
102
103    fn push(&mut self, action: FarmAction<'a>) {
104        let _ = self.inner.push(action);
105    }
106
107    fn clear(&mut self) {
108        self.inner.clear();
109    }
110
111    /// Iterate over the actions.
112    pub fn iter(&self) -> impl Iterator<Item = &FarmAction<'a>> {
113        self.inner.iter()
114    }
115
116    /// Check if empty.
117    pub fn is_empty(&self) -> bool {
118        self.inner.is_empty()
119    }
120
121    /// Number of actions.
122    pub fn len(&self) -> usize {
123        self.inner.len()
124    }
125}
126
127impl Default for FarmActions<'_> {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133impl<'b, 'a> IntoIterator for &'b FarmActions<'a> {
134    type Item = &'b FarmAction<'a>;
135    type IntoIter = core::slice::Iter<'b, FarmAction<'a>>;
136
137    fn into_iter(self) -> Self::IntoIter {
138        self.inner.iter()
139    }
140}
141
142/// Configuration for FARM-1.
143#[derive(Debug, Clone)]
144#[derive(bon::Builder)]
145pub struct FarmConfig {
146    /// Virtual Channel Identifier for CLCW reports.
147    pub vcid: Vcid,
148    /// FARM Sliding Window Width (W). Even, 2..=254.
149    pub window_width: u8,
150}
151
152/// FARM-1 state machine.
153///
154/// Completely synchronous — no I/O, no async.
155/// Generates CLCW reports from its internal state.
156pub struct FarmMachine {
157    state: FarmState,
158    /// V(R): next expected frame sequence number.
159    vr: u8,
160    lockout_flag: bool,
161    wait_flag: bool,
162    retransmit_flag: bool,
163    /// 2-bit counter incremented on BD/BC frames.
164    farm_b_counter: u8,
165    /// Positive window half-width: PW = W/2.
166    pw: u8,
167    /// Negative window half-width: NW = W/2.
168    nw: u8,
169    /// Virtual Channel ID for CLCW.
170    vcid: Vcid,
171}
172
173impl FarmMachine {
174    /// Create a new FARM-1 state machine.
175    pub fn new(config: FarmConfig) -> Self {
176        let pw = config.window_width / 2;
177        let nw = config.window_width / 2;
178        Self {
179            state: FarmState::Open,
180            vr: 0,
181            lockout_flag: false,
182            wait_flag: false,
183            retransmit_flag: false,
184            farm_b_counter: 0,
185            pw,
186            nw,
187            vcid: config.vcid,
188        }
189    }
190
191    /// Current FARM-1 state.
192    pub fn state(&self) -> FarmState {
193        self.state
194    }
195
196    /// Current V(R) (next expected sequence number).
197    pub fn vr(&self) -> u8 {
198        self.vr
199    }
200
201    /// Generate the current CLCW from internal state.
202    pub fn clcw(&self) -> Clcw {
203        let mut clcw = Clcw::new();
204        clcw.set_cop_in_effect(1);
205        clcw.set_vcid(self.vcid);
206        clcw.set_lockout(self.lockout_flag);
207        clcw.set_wait(self.wait_flag);
208        clcw.set_retransmit(self.retransmit_flag);
209        clcw.set_farm_b_counter(self.farm_b_counter);
210        clcw.set_report_value(self.vr);
211        clcw
212    }
213
214    /// Process an event and emit actions.
215    pub fn handle<'a>(
216        &mut self,
217        event: FarmEvent<'a>,
218        actions: &mut FarmActions<'a>,
219    ) {
220        actions.clear();
221
222        match event {
223            FarmEvent::AdFrame {
224                seq,
225                buffer_available,
226                fdu,
227            } => {
228                self.handle_ad_frame(seq, buffer_available, fdu, actions);
229            }
230            FarmEvent::BdFrame { fdu } => {
231                self.handle_bd_frame(fdu, actions);
232            }
233            FarmEvent::BcFrame { command } => {
234                self.handle_bc_frame(command, actions);
235            }
236            FarmEvent::InvalidFrame => {
237                // E9: discard in all states, no state change.
238                actions.push(FarmAction::Discard);
239            }
240            FarmEvent::BufferRelease => {
241                self.handle_buffer_release(actions);
242            }
243            FarmEvent::ClcwReport => {
244                // E11: report CLCW in all states, no state change.
245                actions.push(FarmAction::ClcwReady);
246            }
247        }
248    }
249
250    /// Handle a Type-AD frame (Table 6-1 events E1-E5).
251    fn handle_ad_frame<'a>(
252        &mut self,
253        seq: u8,
254        buffer_available: bool,
255        fdu: &'a [u8],
256        actions: &mut FarmActions<'a>,
257    ) {
258        let zone = self.classify_seq(seq);
259
260        match self.state {
261            FarmState::Open => match zone {
262                SeqZone::Expected => {
263                    if buffer_available {
264                        // E1/S1: accept, advance V(R), clear retransmit
265                        self.vr = self.vr.wrapping_add(1);
266                        self.retransmit_flag = false;
267                        actions.push(FarmAction::Accept { fdu });
268                    } else {
269                        // E2/S1: no buffer, discard, set flags, → Wait
270                        self.retransmit_flag = true;
271                        self.wait_flag = true;
272                        self.state = FarmState::Wait;
273                        actions.push(FarmAction::Discard);
274                    }
275                }
276                SeqZone::PositiveWindow => {
277                    // E3/S1: in positive window but N(S) > V(R)
278                    self.retransmit_flag = true;
279                    actions.push(FarmAction::Discard);
280                }
281                SeqZone::NegativeWindow => {
282                    // E4/S1: retransmitted frame already accepted
283                    actions.push(FarmAction::Discard);
284                }
285                SeqZone::Lockout => {
286                    // E5/S1: outside sliding window → Lockout
287                    self.lockout_flag = true;
288                    self.state = FarmState::Lockout;
289                    actions.push(FarmAction::Discard);
290                }
291            },
292            FarmState::Wait => match zone {
293                SeqZone::Expected => {
294                    // E1/S2: not applicable (spec says N/A)
295                    actions.push(FarmAction::Discard);
296                }
297                SeqZone::PositiveWindow => {
298                    // E3/S2: discard, stay in Wait
299                    actions.push(FarmAction::Discard);
300                }
301                SeqZone::NegativeWindow => {
302                    // E4/S2: discard, stay in Wait
303                    actions.push(FarmAction::Discard);
304                }
305                SeqZone::Lockout => {
306                    // E5/S2: → Lockout
307                    self.lockout_flag = true;
308                    self.state = FarmState::Lockout;
309                    actions.push(FarmAction::Discard);
310                }
311            },
312            FarmState::Lockout => {
313                // E1-E5/S3: discard everything
314                actions.push(FarmAction::Discard);
315            }
316        }
317    }
318
319    /// Handle a Type-BD frame (Table 6-1 event E6).
320    fn handle_bd_frame<'a>(
321        &mut self,
322        fdu: &'a [u8],
323        actions: &mut FarmActions<'a>,
324    ) {
325        // E6: accept in all states, increment FARM-B counter
326        self.farm_b_counter = (self.farm_b_counter + 1) & 0x03;
327        actions.push(FarmAction::Accept { fdu });
328    }
329
330    /// Handle a Type-BC frame (Table 6-1 events E7, E8).
331    fn handle_bc_frame(&mut self, command: ControlCommand, _actions: &mut FarmActions<'_>) {
332        match command {
333            ControlCommand::Unlock => {
334                // E7: Unlock resets FARM-1 to Open in all states
335                self.farm_b_counter = (self.farm_b_counter + 1) & 0x03;
336                self.retransmit_flag = false;
337                match self.state {
338                    FarmState::Open => {
339                        // Already open, just clear flags
340                    }
341                    FarmState::Wait => {
342                        self.wait_flag = false;
343                        self.state = FarmState::Open;
344                    }
345                    FarmState::Lockout => {
346                        self.wait_flag = false;
347                        self.lockout_flag = false;
348                        self.state = FarmState::Open;
349                    }
350                }
351            }
352            ControlCommand::SetVr(new_vr) => {
353                // E8: Set V(R) to V*(R)
354                self.farm_b_counter = (self.farm_b_counter + 1) & 0x03;
355                self.retransmit_flag = false;
356                match self.state {
357                    FarmState::Open => {
358                        self.vr = new_vr;
359                    }
360                    FarmState::Wait => {
361                        self.vr = new_vr;
362                        self.wait_flag = false;
363                        self.state = FarmState::Open;
364                    }
365                    FarmState::Lockout => {
366                        // E8/S3: just increment FARM-B counter,
367                        // do NOT execute the Set V(R). Stay locked.
368                    }
369                }
370            }
371        }
372    }
373
374    /// Handle buffer release signal (Table 6-1 event E10).
375    fn handle_buffer_release(&mut self, actions: &mut FarmActions<'_>) {
376        match self.state {
377            FarmState::Open => {
378                // E10/S1: ignore
379            }
380            FarmState::Wait => {
381                // E10/S2: clear wait flag, → Open
382                self.wait_flag = false;
383                self.state = FarmState::Open;
384            }
385            FarmState::Lockout => {
386                // E10/S3: clear wait flag, stay Lockout
387                self.wait_flag = false;
388            }
389        }
390        let _ = actions;
391    }
392
393    /// Classify a frame sequence number into a sliding window zone.
394    ///
395    /// All arithmetic is modulo 256 per the spec.
396    fn classify_seq(&self, seq: u8) -> SeqZone {
397        if seq == self.vr {
398            return SeqZone::Expected;
399        }
400
401        // Distance from V(R) in the positive direction (mod 256)
402        let pos_dist = seq.wrapping_sub(self.vr);
403        // Distance from V(R) in the negative direction (mod 256)
404        let neg_dist = self.vr.wrapping_sub(seq);
405
406        if pos_dist > 0 && pos_dist < self.pw {
407            SeqZone::PositiveWindow
408        } else if neg_dist > 0 && neg_dist <= self.nw {
409            SeqZone::NegativeWindow
410        } else {
411            SeqZone::Lockout
412        }
413    }
414}
415
416/// Which zone of the FARM sliding window a sequence number falls in.
417#[derive(Debug, Copy, Clone, Eq, PartialEq)]
418enum SeqZone {
419    /// N(S) == V(R): the expected next frame.
420    Expected,
421    /// N(S) > V(R) and within positive window (gap detected).
422    PositiveWindow,
423    /// N(S) < V(R) and within negative window (retransmit).
424    NegativeWindow,
425    /// Outside the sliding window entirely.
426    Lockout,
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432    use crate::ids::Vcid;
433
434    fn make_farm() -> FarmMachine {
435        FarmMachine::new(
436            FarmConfig::builder()
437                .vcid(Vcid::new(0))
438                .window_width(10)
439                .build(),
440        )
441    }
442
443    #[test]
444    fn accept_in_sequence_frame() {
445        let mut farm = make_farm();
446        let fdu = [1, 2, 3];
447        let mut actions = FarmActions::new();
448
449        farm.handle(
450            FarmEvent::AdFrame {
451                seq: 0,
452                buffer_available: true,
453                fdu: &fdu,
454            },
455            &mut actions,
456        );
457
458        assert_eq!(actions.len(), 1);
459        assert!(matches!(
460            actions.iter().next().unwrap(),
461            FarmAction::Accept { .. }
462        ));
463        assert_eq!(farm.vr(), 1);
464        assert!(!farm.clcw().retransmit());
465    }
466
467    #[test]
468    fn no_buffer_transitions_to_wait() {
469        let mut farm = make_farm();
470        let mut actions = FarmActions::new();
471
472        farm.handle(
473            FarmEvent::AdFrame {
474                seq: 0,
475                buffer_available: false,
476                fdu: &[],
477            },
478            &mut actions,
479        );
480
481        assert_eq!(farm.state(), FarmState::Wait);
482        assert!(farm.clcw().wait());
483        assert!(farm.clcw().retransmit());
484    }
485
486    #[test]
487    fn out_of_window_causes_lockout() {
488        let mut farm = make_farm();
489        let mut actions = FarmActions::new();
490
491        // With W=10, PW=5: seq 200 is way outside the window
492        farm.handle(
493            FarmEvent::AdFrame {
494                seq: 200,
495                buffer_available: true,
496                fdu: &[],
497            },
498            &mut actions,
499        );
500
501        assert_eq!(farm.state(), FarmState::Lockout);
502        assert!(farm.clcw().lockout());
503    }
504
505    #[test]
506    fn unlock_resets_from_lockout() {
507        let mut farm = make_farm();
508        let mut actions = FarmActions::new();
509
510        // Force lockout
511        farm.handle(
512            FarmEvent::AdFrame {
513                seq: 200,
514                buffer_available: true,
515                fdu: &[],
516            },
517            &mut actions,
518        );
519        assert_eq!(farm.state(), FarmState::Lockout);
520
521        // Unlock
522        farm.handle(
523            FarmEvent::BcFrame {
524                command: ControlCommand::Unlock,
525            },
526            &mut actions,
527        );
528
529        assert_eq!(farm.state(), FarmState::Open);
530        assert!(!farm.clcw().lockout());
531    }
532
533    #[test]
534    fn set_vr_updates_sequence() {
535        let mut farm = make_farm();
536        let mut actions = FarmActions::new();
537
538        farm.handle(
539            FarmEvent::BcFrame {
540                command: ControlCommand::SetVr(42),
541            },
542            &mut actions,
543        );
544
545        assert_eq!(farm.vr(), 42);
546        assert_eq!(farm.clcw().report_value(), 42);
547    }
548
549    #[test]
550    fn set_vr_ignored_in_lockout() {
551        let mut farm = make_farm();
552        let mut actions = FarmActions::new();
553
554        // Force lockout
555        farm.handle(
556            FarmEvent::AdFrame {
557                seq: 200,
558                buffer_available: true,
559                fdu: &[],
560            },
561            &mut actions,
562        );
563
564        // Set V(R) should NOT work in Lockout (E8/S3)
565        farm.handle(
566            FarmEvent::BcFrame {
567                command: ControlCommand::SetVr(42),
568            },
569            &mut actions,
570        );
571
572        assert_eq!(farm.state(), FarmState::Lockout);
573        assert_eq!(farm.vr(), 0); // unchanged
574    }
575
576    #[test]
577    fn bd_frame_accepted_in_all_states() {
578        let mut farm = make_farm();
579        let mut actions = FarmActions::new();
580
581        // Force lockout
582        farm.handle(
583            FarmEvent::AdFrame {
584                seq: 200,
585                buffer_available: true,
586                fdu: &[],
587            },
588            &mut actions,
589        );
590
591        // BD should still be accepted
592        farm.handle(
593            FarmEvent::BdFrame { fdu: &[0xAA] },
594            &mut actions,
595        );
596
597        assert!(matches!(
598            actions.iter().next().unwrap(),
599            FarmAction::Accept { .. }
600        ));
601    }
602
603    #[test]
604    fn farm_b_counter_increments_on_bd_bc() {
605        let mut farm = make_farm();
606        let mut actions = FarmActions::new();
607
608        farm.handle(
609            FarmEvent::BdFrame { fdu: &[] },
610            &mut actions,
611        );
612        assert_eq!(farm.clcw().farm_b_counter(), 1);
613
614        farm.handle(
615            FarmEvent::BcFrame {
616                command: ControlCommand::Unlock,
617            },
618            &mut actions,
619        );
620        assert_eq!(farm.clcw().farm_b_counter(), 2);
621    }
622
623    #[test]
624    fn buffer_release_transitions_wait_to_open() {
625        let mut farm = make_farm();
626        let mut actions = FarmActions::new();
627
628        // Go to Wait
629        farm.handle(
630            FarmEvent::AdFrame {
631                seq: 0,
632                buffer_available: false,
633                fdu: &[],
634            },
635            &mut actions,
636        );
637        assert_eq!(farm.state(), FarmState::Wait);
638
639        // Buffer release
640        farm.handle(FarmEvent::BufferRelease, &mut actions);
641
642        assert_eq!(farm.state(), FarmState::Open);
643        assert!(!farm.clcw().wait());
644    }
645
646    #[test]
647    fn positive_window_sets_retransmit() {
648        let mut farm = make_farm();
649        let mut actions = FarmActions::new();
650
651        // Seq 3 while V(R)=0, within PW=5
652        farm.handle(
653            FarmEvent::AdFrame {
654                seq: 3,
655                buffer_available: true,
656                fdu: &[],
657            },
658            &mut actions,
659        );
660
661        assert_eq!(farm.state(), FarmState::Open);
662        assert!(farm.clcw().retransmit());
663        assert!(matches!(
664            actions.iter().next().unwrap(),
665            FarmAction::Discard
666        ));
667    }
668
669    #[test]
670    fn negative_window_silently_discards() {
671        let mut farm = make_farm();
672        let mut actions = FarmActions::new();
673
674        // Accept frames 0..3 to advance V(R) to 3
675        for i in 0..3u8 {
676            farm.handle(
677                FarmEvent::AdFrame {
678                    seq: i,
679                    buffer_available: true,
680                    fdu: &[],
681                },
682                &mut actions,
683            );
684        }
685        assert_eq!(farm.vr(), 3);
686
687        // Seq 1 is in the negative window (retransmit of old)
688        farm.handle(
689            FarmEvent::AdFrame {
690                seq: 1,
691                buffer_available: true,
692                fdu: &[],
693            },
694            &mut actions,
695        );
696
697        assert_eq!(farm.state(), FarmState::Open);
698        assert!(!farm.clcw().retransmit());
699        assert!(matches!(
700            actions.iter().next().unwrap(),
701            FarmAction::Discard
702        ));
703    }
704
705    #[test]
706    fn clcw_report_action() {
707        let mut farm = make_farm();
708        let mut actions = FarmActions::new();
709
710        farm.handle(FarmEvent::ClcwReport, &mut actions);
711
712        assert!(matches!(
713            actions.iter().next().unwrap(),
714            FarmAction::ClcwReady
715        ));
716    }
717
718    #[test]
719    fn wrapping_sequence_acceptance() {
720        let mut farm = FarmMachine::new(
721            FarmConfig::builder()
722                .vcid(Vcid::new(0))
723                .window_width(10)
724                .build(),
725        );
726        let fdu_buf = [254u8, 255, 0, 1];
727        let mut actions = FarmActions::new();
728
729        // Set V(R) to 254 so we test wrapping around 255 → 0
730        farm.handle(
731            FarmEvent::BcFrame {
732                command: ControlCommand::SetVr(254),
733            },
734            &mut actions,
735        );
736
737        // Accept 254, 255, 0, 1
738        for (i, &expected_seq) in fdu_buf.iter().enumerate() {
739            farm.handle(
740                FarmEvent::AdFrame {
741                    seq: expected_seq,
742                    buffer_available: true,
743                    fdu: &fdu_buf[i..i + 1],
744                },
745                &mut actions,
746            );
747            assert!(
748                matches!(
749                    actions.iter().next().unwrap(),
750                    FarmAction::Accept { .. }
751                ),
752                "should accept seq {expected_seq}"
753            );
754        }
755        assert_eq!(farm.vr(), 2);
756    }
757}