1use crate::block::{BlockCode, container_traits, expect_len};
70use crate::rs::ReedSolomon;
71use comnum_core::signal::hard_decisions;
72use comnum_core::{Result, ensure};
73
74mod ldpc;
75mod turbo;
76
77pub use ldpc::{Ar4jaRate, CcsdsLdpc};
78pub use turbo::{CcsdsTurbo, TURBO_LENGTHS, TurboRate, turbo_permutation};
79
80const DUAL_BASIS: [u8; 8] = [0x8D, 0xEF, 0xEC, 0x86, 0xFA, 0x99, 0xAF, 0x7B];
83const DUAL_BASIS_INV: [u8; 8] = [0xC5, 0x42, 0x2E, 0xFD, 0xF0, 0x79, 0xAC, 0xCC];
85const RS_FIELD: u32 = 0x187;
87pub const RS_DEPTHS: [usize; 6] = [1, 2, 3, 4, 5, 8];
89
90pub fn to_dual_basis(u: u8) -> u8 {
94 (0..8).filter(|r| (u >> (7 - r)) & 1 == 1).fold(0, |z, r| z ^ DUAL_BASIS[r])
95}
96
97pub fn from_dual_basis(z: u8) -> u8 {
100 (0..8).filter(|r| (z >> (7 - r)) & 1 == 1).fold(0, |u, r| u ^ DUAL_BASIS_INV[r])
101}
102
103#[derive(Clone, Debug, PartialEq)]
107pub struct CcsdsRs {
108 rs: ReedSolomon,
109 depth: usize,
110 fill: usize,
111}
112
113impl CcsdsRs {
114 pub fn new(e: usize, depth: usize, fill: usize) -> Result<CcsdsRs> {
121 ensure(e == 8 || e == 16, || format!("the CCSDS Reed-Solomon codes correct E = 8 or 16 symbols, got {e}"))?;
122 ensure(RS_DEPTHS.contains(&depth), || {
123 format!("the interleaving depth must be 1, 2, 3, 4, 5 or 8, got {depth}")
124 })?;
125 ensure(fill < 255 - 2 * e, || format!("a virtual fill of {fill} symbols leaves no information"))?;
126 let rs = ReedSolomon::with_field(8, RS_FIELD, 128 - e, 11, e, 255 - fill)?;
127 Ok(CcsdsRs { rs, depth, fill })
128 }
129
130 pub fn e(&self) -> usize {
132 self.rs.t()
133 }
134
135 pub fn depth(&self) -> usize {
137 self.depth
138 }
139
140 pub fn fill(&self) -> usize {
142 self.fill
143 }
144
145 pub fn code(&self) -> &ReedSolomon {
148 &self.rs
149 }
150
151 pub fn frame_len(&self) -> usize {
153 self.rs.k_symbols() * self.depth
154 }
155
156 pub fn codeblock_len(&self) -> usize {
158 self.rs.n_symbols() * self.depth
159 }
160
161 pub fn encode_bytes(&self, frame: &[u8]) -> Result<Vec<u8>> {
167 expect_len(frame.len(), self.frame_len(), "octets")?;
168 let (k, i_depth) = (self.rs.k_symbols(), self.depth);
169 let parity = 2 * self.e();
170 let mut out = frame.to_vec();
171 out.resize(self.codeblock_len(), 0);
172 for i in 0..i_depth {
173 let msg: Vec<u16> = (0..k).map(|t| u16::from(from_dual_basis(frame[t * i_depth + i]))).collect();
174 let word = self.rs.encode_symbols(&msg)?;
175 for (t, &s) in word[k..].iter().enumerate() {
176 out[(k + t) * i_depth + i] = to_dual_basis(s as u8);
177 }
178 debug_assert_eq!(word.len(), k + parity);
179 }
180 Ok(out)
181 }
182
183 pub fn decode_bytes(&self, block: &[u8]) -> Result<(Vec<u8>, Option<usize>)> {
190 expect_len(block.len(), self.codeblock_len(), "octets")?;
191 let (n, k, i_depth) = (self.rs.n_symbols(), self.rs.k_symbols(), self.depth);
192 let mut frame = block[..self.frame_len()].to_vec();
193 let mut total = Some(0);
194 for i in 0..i_depth {
195 let r: Vec<u16> = (0..n).map(|t| u16::from(from_dual_basis(block[t * i_depth + i]))).collect();
196 let (msg, fixed) = self.rs.decode_symbols(&r)?;
197 match fixed {
198 Some(c) => {
199 total = total.map(|t| t + c);
200 for (t, &s) in msg.iter().enumerate().take(k) {
201 frame[t * i_depth + i] = to_dual_basis(s as u8);
202 }
203 }
204 None => total = None,
205 }
206 }
207 Ok((frame, total))
208 }
209}
210
211fn bits_to_bytes(bits: &[u8]) -> Vec<u8> {
212 bits.chunks(8).map(|b| b.iter().fold(0u8, |acc, &v| (acc << 1) | (v & 1))).collect()
213}
214
215fn bytes_to_bits(bytes: &[u8]) -> Vec<u8> {
216 bytes.iter().flat_map(|&b| (0..8).rev().map(move |k| (b >> k) & 1)).collect()
217}
218
219impl BlockCode for CcsdsRs {
220 fn k(&self) -> usize {
221 8 * self.frame_len()
222 }
223
224 fn n(&self) -> usize {
225 8 * self.codeblock_len()
226 }
227
228 fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
229 expect_len(info.len(), self.k(), "bits")?;
230 Ok(bytes_to_bits(&self.encode_bytes(&bits_to_bytes(info))?))
231 }
232
233 fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
235 expect_len(llr.len(), self.n(), "LLRs")?;
236 let (frame, _) = self.decode_bytes(&bits_to_bytes(&hard_decisions(llr)))?;
237 Ok(bytes_to_bits(&frame))
238 }
239}
240
241container_traits!(CcsdsRs);
242
243#[cfg(test)]
244mod tests {
245 use super::ldpc::ar4ja_pi;
246 use super::*;
247 use comnum_core::rng::Rng;
248 use comnum_math::gf2m::Field;
249
250 fn random_bits(n: usize, rng: &mut Rng) -> Vec<u8> {
251 (0..n).map(|_| rng.bit()).collect()
252 }
253
254 fn bpsk(c: &[u8], amplitude: f64) -> Vec<f64> {
255 c.iter().map(|&b| if b == 0 { amplitude } else { -amplitude }).collect()
256 }
257
258 #[test]
259 fn the_dual_basis_matrices_are_inverse_and_reproduce_annex_f() {
260 for u in 0..=255u8 {
261 assert_eq!(from_dual_basis(to_dual_basis(u)), u);
262 }
263 assert_eq!(from_dual_basis(0b1011_1001), 0b0010_1010);
265 let f = Field::new(8, RS_FIELD).unwrap();
266 assert_eq!(f.pow(213), 0b0010_1010);
267 assert_eq!(f.pow(152), 0b0101_1001);
269 assert_eq!(to_dual_basis(0b0101_1001), 0b1110_1000);
270 }
271
272 #[test]
273 fn the_generators_are_those_of_annex_g() {
274 let f = Field::new(8, RS_FIELD).unwrap();
275 let g16 = [0, 249, 59, 66, 4, 43, 126, 251, 97, 30, 3, 213, 50, 66, 170, 5, 24];
277 let g8 = [0, 30, 230, 49, 235, 129, 81, 76, 173];
278 for (e, half) in [(16, &g16[..]), (8, &g8[..])] {
279 let rs = CcsdsRs::new(e, 1, 0).unwrap();
280 let g = rs.code().generator();
281 assert_eq!(g.len(), 2 * e + 1);
282 for (i, &x) in half.iter().enumerate() {
283 assert_eq!(u32::from(g[i]), f.pow(x), "E = {e}, G{i}");
284 assert_eq!(g[i], g[2 * e - i], "E = {e}, G{i} = G{}", 2 * e - i);
285 }
286 }
287 }
288
289 #[test]
290 fn a_codeblock_corrects_e_symbols_per_codeword() {
291 let mut rng = Rng::new(1);
292 for (e, depth, fill) in [(16, 5, 0), (8, 2, 11), (16, 8, 3)] {
293 let rs = CcsdsRs::new(e, depth, fill).unwrap();
294 assert_eq!(rs.codeblock_len() - rs.frame_len(), 2 * e * depth);
295 let frame: Vec<u8> = (0..rs.frame_len()).map(|_| rng.below(256) as u8).collect();
296 let mut block = rs.encode_bytes(&frame).unwrap();
297 assert_eq!(&block[..frame.len()], &frame[..], "systematic");
298 for i in 0..depth {
300 for t in 0..e {
301 block[(3 * t + 1) * depth + i] ^= 0x5A;
302 }
303 }
304 let (out, fixed) = rs.decode_bytes(&block).unwrap();
305 assert_eq!(out, frame, "E = {e}, I = {depth}, q = {fill}");
306 assert_eq!(fixed, Some(e * depth));
307 }
308 assert!(CcsdsRs::new(16, 6, 0).is_err() && CcsdsRs::new(12, 1, 0).is_err());
309 }
310
311 #[test]
312 fn the_rate_7_8_code_is_that_of_table_c1() {
313 let code = CcsdsLdpc::c2().unwrap();
314 assert_eq!((code.k(), code.n()), (7136, 8160));
315 let mut rng = Rng::new(2);
316 let info = random_bits(7136, &mut rng);
317 assert!(code.ldpc().is_codeword(&code.full_codeword(&info).unwrap()));
319 let c = code.encode_block(&info).unwrap();
320 assert_eq!((c.len(), c[..7136] == info[..], &c[8158..]), (8160, true, &[0u8, 0][..]));
321 let llr: Vec<f64> = bpsk(&c, 1.0).iter().map(|v| 2.0 / 0.16 * (v + 0.4 * rng.gaussian())).collect();
323 assert_eq!(code.decode_block(&llr).unwrap(), info);
324 }
325
326 #[test]
327 fn the_ar4ja_codes_have_the_lengths_of_table_7_5_and_encode_codewords() {
328 let mut rng = Rng::new(3);
329 for (k, rate, n) in
330 [(1024, Ar4jaRate::R1_2, 2048), (1024, Ar4jaRate::R2_3, 1536), (1024, Ar4jaRate::R4_5, 1280)]
331 {
332 let code = CcsdsLdpc::ar4ja(k, rate).unwrap();
333 assert_eq!((code.k(), code.n()), (k, n), "rate {}", rate.name());
334 let info = random_bits(k, &mut rng);
335 assert!(code.ldpc().is_codeword(&code.full_codeword(&info).unwrap()), "rate {}", rate.name());
336 let c = code.encode_block(&info).unwrap();
337 assert_eq!(&c[..k], &info[..]);
338 let llr: Vec<f64> = bpsk(&c, 1.0).iter().map(|v| 2.0 / 0.16 * (v + 0.4 * rng.gaussian())).collect();
339 assert_eq!(code.decode_block(&llr).unwrap(), info, "rate {}", rate.name());
340 }
341 assert!(CcsdsLdpc::ar4ja(2048, Ar4jaRate::R1_2).is_err());
342 }
343
344 #[test]
345 fn the_permutations_of_the_ar4ja_codes_are_permutations() {
346 for m in [128, 256, 512, 1024, 2048, 4096, 8192] {
347 for k in 1..=26 {
348 let mut seen = vec![false; m];
349 for i in 0..m {
350 seen[ar4ja_pi(k, i, m)] = true;
351 }
352 assert!(seen.iter().all(|&s| s), "Pi_{k}, M = {m}");
353 }
354 }
355 }
356
357 #[test]
358 fn the_turbo_codes_have_the_lengths_of_table_6_2() {
359 let table = [
360 (1784, [3576, 5364, 7152, 10728]),
361 (3568, [7144, 10716, 14288, 21432]),
362 (7136, [14280, 21420, 28560, 42840]),
363 (8920, [17848, 26772, 35696, 53544]),
364 ];
365 for (k, ns) in table {
366 for (r, n) in [TurboRate::R1_2, TurboRate::R1_3, TurboRate::R1_4, TurboRate::R1_6].into_iter().zip(ns) {
367 assert_eq!(CcsdsTurbo::new(k, r).unwrap().n(), n, "k = {k}, rate {}", r.name());
368 }
369 }
370 assert!(CcsdsTurbo::new(1000, TurboRate::R1_2).is_err());
371 }
372
373 #[test]
374 fn the_turbo_permutation_starts_as_computed_from_sec_6_3_g() {
375 for k in TURBO_LENGTHS {
377 let p = turbo_permutation(k).unwrap();
378 let first: Vec<usize> = p.perm()[..10].iter().map(|v| v + 1).collect();
379 assert_eq!(first, [4, 171, 300, 467, 596, 763, 892, 1059, 1188, 1355], "k = {k}");
380 }
381 }
382
383 #[test]
384 fn the_turbo_codes_terminate_and_decode() {
385 let mut rng = Rng::new(4);
386 for rate in [TurboRate::R1_2, TurboRate::R1_3, TurboRate::R1_4, TurboRate::R1_6] {
387 let code = CcsdsTurbo::new(1784, rate).unwrap();
388 let info = random_bits(1784, &mut rng);
389 let c = code.encode_block(&info).unwrap();
390 assert_eq!(c.len(), code.n());
391 let per = c.len() / (1784 + 4);
394 if rate != TurboRate::R1_2 {
395 let sys: Vec<u8> = (0..1784).map(|t| c[t * per]).collect();
396 assert_eq!(sys, info);
397 }
398 let llr: Vec<f64> = bpsk(&c, 1.0).iter().map(|v| 2.0 * (v + 0.5 * rng.gaussian())).collect();
399 assert_eq!(code.decode_block(&llr).unwrap(), info, "rate {}", rate.name());
400 }
401 }
402}