Skip to main content

leodos_protocols/coding/
randomizer.rs

1//! CCSDS-compliant pseudo-randomization for TC and TM frames.
2//!
3//! Spec: https://ccsds.org/Pubs/131x0b5.pdf
4//!
5//! Randomization is the process of XORing the frame data with a standard,
6//! pre-defined sequence. This is done to ensure the final transmitted data has
7//! frequent bit transitions (0-to-1 and 1-to-0), which is essential for helping
8//! the receiver's hardware maintain clock synchronization with the signal.
9//!
10//! The randomization sequence is its own inverse; applying the same operation
11//! twice restores the original data.
12
13/// A trait for applying a specific CCSDS randomization algorithm.
14pub trait Randomizer {
15    /// Applies or removes the randomization sequence in-place on the provided buffer.
16    fn apply(&self, buffer: &mut [u8]) {
17        for (byte, &rand) in buffer.iter_mut().zip(self.table().iter().cycle()) {
18            *byte ^= rand;
19        }
20    }
21    /// Returns the randomization lookup table bytes.
22    fn table(&self) -> &[u8];
23}
24
25/// The standard randomizer for Telecommand (TC) frames.
26pub struct TcRandomizer([u8; 255]);
27/// The legacy 255-byte randomizer for Telemetry (TM) frames.
28pub struct Tm255Randomizer([u8; 255]);
29/// The recommended 131071-byte randomizer for Telemetry (TM) frames.
30///
31/// **Warning:** Using this will embed a 128 KB lookup table in your binary.
32pub struct Tm131071Randomizer([u8; 131071]);
33
34impl TcRandomizer {
35    /// Creates a new instance of the TC randomizer.
36    pub const fn new() -> Self {
37        let mut table = [0u8; 255];
38        let mut lfsr = 0xFF_u8;
39        let mut i = 0;
40        while i < 255 {
41            let mut val = 0_u8;
42            let mut bit = 0;
43            while bit < 8 {
44                val = (val << 1) | (lfsr & 1);
45                let extra_bit =
46                    (lfsr ^ (lfsr >> 1) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 4) ^ (lfsr >> 6))
47                        & 1;
48                lfsr = (lfsr >> 1) | (extra_bit << 7);
49                bit += 1;
50            }
51            table[i] = val;
52            i += 1;
53        }
54        TcRandomizer(table)
55    }
56}
57
58impl Randomizer for TcRandomizer {
59    fn table(&self) -> &[u8] {
60        &self.0
61    }
62}
63
64// --- TM Randomizers (new code) ---
65
66impl Tm255Randomizer {
67    /// Creates a new instance of the legacy 255-byte TM randomizer.
68    pub const fn new() -> Self {
69        let mut table = [0u8; 255];
70        let mut lfsr = 0xFF_u8;
71        let mut i = 0;
72        while i < 255 {
73            let mut val = 0_u8;
74            let mut bit = 0;
75            while bit < 8 {
76                val = (val << 1) | (lfsr & 1);
77                let extra_bit = (lfsr ^ (lfsr >> 3) ^ (lfsr >> 5) ^ (lfsr >> 7)) & 1;
78                lfsr = (lfsr >> 1) | (extra_bit << 7);
79                bit += 1;
80            }
81            table[i] = val;
82            i += 1;
83        }
84        Tm255Randomizer(table)
85    }
86}
87
88impl Randomizer for Tm255Randomizer {
89    fn table(&self) -> &[u8] {
90        &self.0
91    }
92}
93
94impl Tm131071Randomizer {
95    /// Creates a new instance of the 131071-byte TM randomizer.
96    pub const fn new() -> Self {
97        let mut table = [0u8; 131071];
98        let mut lfsr = 0x1FFFF_u32; // 17 bits set to 1
99        let mut i = 0;
100        while i < 131071 {
101            let mut val = 0_u8;
102            let mut bit = 0;
103            while bit < 8 {
104                val = (val << 1) | ((lfsr & 1) as u8);
105                let extra_bit = (lfsr ^ (lfsr >> 3)) & 1; // Corresponds to x^17 + x^14 + 1
106                lfsr = (lfsr >> 1) | (extra_bit << 16);
107                bit += 1;
108            }
109            table[i] = val;
110            i += 1;
111        }
112        Tm131071Randomizer(table)
113    }
114}
115
116impl Randomizer for Tm131071Randomizer {
117    fn table(&self) -> &[u8] {
118        &self.0
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn tc_randomization_roundtrip() {
128        // Initialize an array on the stack. No heap allocation.
129        let mut data = [0u8; 512];
130        for i in 0..data.len() {
131            data[i] = (i % 256) as u8;
132        }
133        // Manually copy the array to create the 'original' version.
134        let original = data;
135        let randomizer = TcRandomizer::new();
136
137        randomizer.apply(&mut data);
138
139        assert_ne!(original, data, "Randomized data should not match original");
140
141        randomizer.apply(&mut data);
142        assert_eq!(original, data, "De-randomized data should match original");
143    }
144
145    #[test]
146    fn tm_255_randomization_roundtrip() {
147        let mut data = [0u8; 512];
148        for i in 0..data.len() {
149            data[i] = (i % 256) as u8;
150        }
151        let original = data;
152        let randomizer = Tm255Randomizer::new();
153
154        randomizer.apply(&mut data);
155        assert_ne!(original, data, "Randomized data should not match original");
156
157        randomizer.apply(&mut data);
158        assert_eq!(original, data, "De-randomized data should match original");
159    }
160
161    // Note: We don't add a test for the 131071-byte randomizer here
162    // because creating a 128KB array on the stack could cause a stack overflow
163    // in the test environment. The logic is identical to the others, so this
164    // is a reasonable omission.
165}