Skip to main content

leodos_protocols/datalink/reliability/cop1/
clcw.rs

1//! Communications Link Control Word (CLCW).
2//!
3//! CCSDS 232.1-B-2, carried in the OCF of TM/AOS frames.
4//! The CLCW is a 32-bit word generated by FARM-1 to report its
5//! state back to FOP-1.
6//!
7//! ```text
8//! Bit  0     : Control Word Type (0 = CLCW)
9//! Bits 1-2   : CLCW Version Number
10//! Bits 3-5   : Status Field
11//! Bits 6-7   : COP in Effect (01 = COP-1)
12//! Bits 8-13  : Virtual Channel Identifier
13//! Bits 14-15 : Reserved Spare
14//! Bit  16    : No RF Available Flag
15//! Bit  17    : No Bit Lock Flag
16//! Bit  18    : Lockout Flag
17//! Bit  19    : Wait Flag
18//! Bit  20    : Retransmit Flag
19//! Bits 21-22 : FARM-B Counter (2 bits)
20//! Bit  23    : Reserved Spare
21//! Bits 24-31 : Report Value (N(R) = V(R))
22//! ```
23
24use crate::ids::Vcid;
25use zerocopy::FromBytes;
26use zerocopy::Immutable;
27use zerocopy::IntoBytes;
28use zerocopy::KnownLayout;
29use zerocopy::Unaligned;
30use zerocopy::byteorder::network_endian::U32;
31
32/// A 32-bit Communications Link Control Word.
33#[repr(C)]
34#[derive(
35    FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable,
36    Debug, Copy, Clone, Eq, PartialEq,
37)]
38pub struct Clcw {
39    word: U32,
40}
41
42#[rustfmt::skip]
43mod bitmask {
44    pub const TYPE_MASK:        u32 = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
45    pub const VERSION_MASK:     u32 = 0b_0110_0000_0000_0000_0000_0000_0000_0000;
46    pub const STATUS_MASK:      u32 = 0b_0001_1100_0000_0000_0000_0000_0000_0000;
47    pub const COP_MASK:         u32 = 0b_0000_0011_0000_0000_0000_0000_0000_0000;
48    pub const VCID_MASK:        u32 = 0b_0000_0000_1111_1100_0000_0000_0000_0000;
49    pub const NO_RF_MASK:       u32 = 0b_0000_0000_0000_0000_1000_0000_0000_0000;
50    pub const NO_BIT_LOCK_MASK: u32 = 0b_0000_0000_0000_0000_0100_0000_0000_0000;
51    pub const LOCKOUT_MASK:     u32 = 0b_0000_0000_0000_0000_0010_0000_0000_0000;
52    pub const WAIT_MASK:        u32 = 0b_0000_0000_0000_0000_0001_0000_0000_0000;
53    pub const RETRANSMIT_MASK:  u32 = 0b_0000_0000_0000_0000_0000_1000_0000_0000;
54    pub const FARM_B_MASK:      u32 = 0b_0000_0000_0000_0000_0000_0110_0000_0000;
55    pub const REPORT_MASK:      u32 = 0b_0000_0000_0000_0000_0000_0000_1111_1111;
56}
57
58use bitmask::*;
59
60fn get(word: U32, mask: u32) -> u32 {
61    (word.get() & mask) >> mask.trailing_zeros()
62}
63
64fn set(word: &mut U32, mask: u32, value: u32) {
65    word.set((word.get() & !mask) | (value << mask.trailing_zeros()));
66}
67
68impl Clcw {
69    /// Size of a CLCW in bytes.
70    pub const SIZE: usize = 4;
71
72    /// Create a new CLCW with all fields zeroed.
73    pub fn new() -> Self {
74        Self { word: U32::ZERO }
75    }
76
77    /// Parse a CLCW from a 4-byte slice.
78    pub fn parse(bytes: &[u8]) -> Option<&Self> {
79        Self::ref_from_bytes(bytes).ok()
80    }
81
82    /// Control Word Type (always 0 for CLCW).
83    pub fn control_word_type(&self) -> u8 {
84        get(self.word, TYPE_MASK) as u8
85    }
86
87    /// CLCW version number (should be 0).
88    pub fn version(&self) -> u8 {
89        get(self.word, VERSION_MASK) as u8
90    }
91    /// Sets the CLCW version.
92    pub fn set_version(&mut self, v: u8) {
93        set(&mut self.word, VERSION_MASK, v as u32);
94    }
95
96    /// Status field (3 bits, mission-specific).
97    pub fn status(&self) -> u8 {
98        get(self.word, STATUS_MASK) as u8
99    }
100    /// Sets the status field.
101    pub fn set_status(&mut self, v: u8) {
102        set(&mut self.word, STATUS_MASK, v as u32);
103    }
104
105    /// COP in effect (01 = COP-1).
106    pub fn cop_in_effect(&self) -> u8 {
107        get(self.word, COP_MASK) as u8
108    }
109    /// Sets the COP in effect field.
110    pub fn set_cop_in_effect(&mut self, v: u8) {
111        set(&mut self.word, COP_MASK, v as u32);
112    }
113
114    /// Virtual Channel Identifier (6 bits).
115    pub fn vcid(&self) -> Vcid {
116        Vcid::new(get(self.word, VCID_MASK) as u32)
117    }
118    /// Sets the VCID.
119    pub fn set_vcid(&mut self, v: Vcid) {
120        set(&mut self.word, VCID_MASK, v.get());
121    }
122
123    /// No RF Available flag.
124    pub fn no_rf_available(&self) -> bool {
125        get(self.word, NO_RF_MASK) != 0
126    }
127    /// Sets the No RF Available flag.
128    pub fn set_no_rf_available(&mut self, v: bool) {
129        set(&mut self.word, NO_RF_MASK, v as u32);
130    }
131
132    /// No Bit Lock flag.
133    pub fn no_bit_lock(&self) -> bool {
134        get(self.word, NO_BIT_LOCK_MASK) != 0
135    }
136    /// Sets the No Bit Lock flag.
137    pub fn set_no_bit_lock(&mut self, v: bool) {
138        set(&mut self.word, NO_BIT_LOCK_MASK, v as u32);
139    }
140
141    /// Lockout flag (FARM-1 is in Lockout state).
142    pub fn lockout(&self) -> bool {
143        get(self.word, LOCKOUT_MASK) != 0
144    }
145    /// Sets the Lockout flag.
146    pub fn set_lockout(&mut self, v: bool) {
147        set(&mut self.word, LOCKOUT_MASK, v as u32);
148    }
149
150    /// Wait flag (FARM-1 is in Wait state).
151    pub fn wait(&self) -> bool {
152        get(self.word, WAIT_MASK) != 0
153    }
154    /// Sets the Wait flag.
155    pub fn set_wait(&mut self, v: bool) {
156        set(&mut self.word, WAIT_MASK, v as u32);
157    }
158
159    /// Retransmit flag (a frame was lost or discarded).
160    pub fn retransmit(&self) -> bool {
161        get(self.word, RETRANSMIT_MASK) != 0
162    }
163    /// Sets the Retransmit flag.
164    pub fn set_retransmit(&mut self, v: bool) {
165        set(&mut self.word, RETRANSMIT_MASK, v as u32);
166    }
167
168    /// FARM-B counter (2-bit, incremented on BD/BC frames).
169    pub fn farm_b_counter(&self) -> u8 {
170        get(self.word, FARM_B_MASK) as u8
171    }
172    /// Sets the FARM-B counter.
173    pub fn set_farm_b_counter(&mut self, v: u8) {
174        set(&mut self.word, FARM_B_MASK, (v & 0x03) as u32);
175    }
176
177    /// Report value N(R) = V(R), the next expected frame sequence number.
178    pub fn report_value(&self) -> u8 {
179        get(self.word, REPORT_MASK) as u8
180    }
181    /// Sets the report value N(R).
182    pub fn set_report_value(&mut self, v: u8) {
183        set(&mut self.word, REPORT_MASK, v as u32);
184    }
185}
186
187impl Default for Clcw {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::ids::Vcid;
197
198    #[test]
199    fn roundtrip_fields() {
200        let mut clcw = Clcw::new();
201
202        clcw.set_cop_in_effect(1);
203        clcw.set_vcid(Vcid::new(42));
204        clcw.set_lockout(true);
205        clcw.set_wait(false);
206        clcw.set_retransmit(true);
207        clcw.set_farm_b_counter(3);
208        clcw.set_report_value(0xAB);
209
210        assert_eq!(clcw.control_word_type(), 0);
211        assert_eq!(clcw.cop_in_effect(), 1);
212        assert_eq!(clcw.vcid(), Vcid::new(42));
213        assert!(clcw.lockout());
214        assert!(!clcw.wait());
215        assert!(clcw.retransmit());
216        assert_eq!(clcw.farm_b_counter(), 3);
217        assert_eq!(clcw.report_value(), 0xAB);
218    }
219
220    #[test]
221    fn size_is_4_bytes() {
222        assert_eq!(core::mem::size_of::<Clcw>(), 4);
223    }
224
225    #[test]
226    fn parse_from_bytes() {
227        let mut clcw = Clcw::new();
228        clcw.set_report_value(99);
229        let bytes = zerocopy::IntoBytes::as_bytes(&clcw);
230        let parsed = Clcw::parse(bytes).unwrap();
231        assert_eq!(parsed.report_value(), 99);
232    }
233}