Skip to main content

leodos_protocols/coding/fec/
ldpc.rs

1//! CCSDS LDPC (Low-Density Parity-Check) Forward Error Correction
2//!
3//! Implements the AR4JA LDPC codes specified in CCSDS 131.0-B-5.
4//!
5//! # Supported TM Codes
6//!
7//! | Code   | Rate | k     | n     | Circulant | M (block) |
8//! |--------|------|-------|-------|-----------|-----------|
9//! | TM1280 | 4/5  | 1024  | 1280  | 32        | 128       |
10//! | TM1536 | 2/3  | 1024  | 1536  | 64        | 256       |
11//! | TM2048 | 1/2  | 1024  | 2048  | 128       | 512       |
12//! | TM5120 | 4/5  | 4096  | 5120  | 128       | 512       |
13//! | TM6144 | 2/3  | 4096  | 6144  | 256       | 1024      |
14//! | TM8192 | 1/2  | 4096  | 8192  | 512       | 2048      |
15//!
16//! # Encoding
17//!
18//! Systematic encoding using compact generator matrices derived from
19//! the CCSDS AR4JA parity-check matrix. The generator exploits the
20//! circulant structure: one row per circulant block is stored, and
21//! the other rows are obtained by rotation.
22//!
23//! # Decoding
24//!
25//! Layered min-sum belief propagation with fixed-point (i16) LLRs.
26//! The H matrix is expanded on-the-fly using the PI_K permutation
27//! function defined by theta/phi lookup tables per CCSDS 131.0-B-5.
28
29// ── Code Parameters ──────────────────────────────────────────────
30
31/// Describes a CCSDS AR4JA LDPC code.
32#[derive(Debug, Clone, Copy)]
33pub struct LdpcCode {
34    /// Transmitted codeword length in bits.
35    pub n: usize,
36    /// Information block length in bits.
37    pub k: usize,
38    /// Number of punctured bits (not transmitted).
39    pub punctured: usize,
40    /// Circulant size for the compact generator.
41    pub circulant_size: usize,
42    /// Block size M for H matrix sub-matrices.
43    pub submatrix_size: usize,
44    /// Number of base matrix rows (always 3 for AR4JA).
45    pub mb: usize,
46    /// Number of base matrix columns.
47    pub nb: usize,
48    /// Reference to compact generator matrix.
49    pub generator: &'static [u64],
50    /// Reference to compact H matrix (3 layers, mb×nb each).
51    pub h_matrix: &'static [[[u8; 11]; 4]; 3],
52    /// Reference to theta lookup table.
53    pub theta: &'static [u8; 26],
54    /// Reference to phi lookup table (4 × 26).
55    pub phi: &'static [[u16; 26]; 4],
56}
57
58impl LdpcCode {
59    /// Number of parity bits (n - k).
60    pub fn parity_bits(&self) -> usize {
61        self.n - self.k
62    }
63
64    /// Number of info bytes.
65    pub fn info_bytes(&self) -> usize {
66        self.k / 8
67    }
68
69    /// Number of codeword bytes.
70    pub fn codeword_bytes(&self) -> usize {
71        self.n / 8
72    }
73
74    /// Number of parity bytes.
75    pub fn parity_bytes(&self) -> usize {
76        self.parity_bits() / 8
77    }
78}
79
80/// Errors from LDPC operations.
81#[derive(Debug, Copy, Clone, Eq, PartialEq)]
82pub enum LdpcError {
83    /// Buffer has wrong size.
84    WrongSize {
85        /// Expected size.
86        expected: usize,
87        /// Provided size.
88        provided: usize,
89    },
90    /// Decoding failed (did not converge).
91    DecodingFailed,
92}
93
94// ── H Matrix Block Types ─────────────────────────────────────────
95
96/// All-zero M×M block.
97const HZ: u8 = 0 << 6;
98/// Identity M×M block.
99const HI: u8 = 1 << 6;
100/// PI_K permutation block (lower 6 bits = k-1 index).
101const HP: u8 = 2 << 6;
102
103/// Block type mask (upper 2 bits).
104const TYPE_MASK: u8 = 0xC0;
105/// PI_K index mask (lower 6 bits).
106const INDEX_MASK: u8 = 0x3F;
107
108// ── PI_K Permutation (CCSDS 131.0-B-5 Section 7) ────────────────
109
110/// Computes pi_k(i) for the PI_K permutation.
111///
112/// pi_k(i) = (M/4) * ((theta_k + floor(4i/M)) mod 4)
113///         + (phi_{floor(4i/M),k}(M) + i) mod (M/4)
114fn pi_k(
115    i: usize,
116    k_idx: usize,
117    m: usize,
118    theta: &[u8; 26],
119    phi: &[[u16; 26]; 4],
120) -> usize {
121    let m4 = m / 4;
122    let j = 4 * i / m; // floor(4i/M), range 0..3
123    let theta_val = theta[k_idx] as usize;
124    let phi_val = phi[j][k_idx] as usize;
125    m4 * ((theta_val + j) % 4) + (phi_val + i) % m4
126}
127
128// ── Theta/Phi Lookup Tables ──────────────────────────────────────
129
130/// Theta constants (CCSDS 131.0-B-5, Table 7-4).
131/// Index is k-1 for k=1..26.
132static THETA_K: [u8; 26] = [
133    3, 0, 1, 2, 2, 3, 0, 1, 0, 1, 2, 0, 2,
134    3, 0, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2, 3,
135];
136
137/// Phi constants for M=128.
138static PHI_M128: [[u16; 26]; 4] = [
139    [1, 22, 0, 26, 0, 10, 5, 18, 3, 22, 3, 8, 25, 25, 2, 27, 7, 7, 15, 10, 4,
140     19, 7, 9, 26, 17],
141    [0, 27, 30, 28, 7, 1, 8, 20, 26, 24, 4, 12, 23, 15, 15, 22, 31, 3, 29, 21,
142     2, 5, 11, 26, 9, 17],
143    [0, 12, 30, 18, 10, 16, 13, 9, 7, 15, 16, 18, 4, 23, 5, 3, 29, 11, 4, 8, 2,
144     11, 11, 3, 15, 13],
145    [0, 13, 19, 14, 15, 20, 17, 4, 4, 11, 17, 20, 8, 22, 19, 15, 5, 21, 17, 9,
146     20, 18, 31, 13, 2, 18],
147];
148
149/// Phi constants for M=256.
150static PHI_M256: [[u16; 26]; 4] = [
151    [59, 18, 52, 23, 11, 7, 22, 25, 27, 30, 43, 14, 46, 62, 44, 12, 38, 47, 1,
152     52, 61, 10, 55, 7, 12, 2],
153    [0, 32, 21, 36, 30, 29, 44, 29, 39, 14, 22, 15, 48, 55, 39, 11, 1, 50, 40,
154     62, 27, 38, 40, 15, 11, 18],
155    [0, 46, 45, 27, 48, 37, 41, 13, 9, 49, 36, 10, 11, 18, 54, 40, 27, 35, 25,
156     46, 24, 33, 18, 37, 35, 21],
157    [0, 44, 51, 12, 15, 12, 4, 7, 2, 30, 53, 23, 29, 37, 42, 48, 4, 10, 18, 56,
158     9, 11, 23, 8, 7, 24],
159];
160
161/// Phi constants for M=512.
162static PHI_M512: [[u16; 26]; 4] = [
163    [16, 103, 105, 0, 50, 29, 115, 30, 92, 78, 70, 66, 39, 84, 79, 70, 29, 32,
164     45, 113, 86, 1, 42, 118, 33, 126],
165    [0, 53, 74, 45, 47, 0, 59, 102, 25, 3, 88, 65, 62, 68, 91, 70, 115, 31,
166     121, 45, 56, 54, 108, 14, 30, 116],
167    [0, 8, 119, 89, 31, 122, 1, 69, 92, 47, 11, 31, 19, 66, 49, 81, 96, 38, 83,
168     42, 58, 24, 25, 92, 38, 120],
169    [0, 35, 97, 112, 64, 93, 99, 94, 103, 91, 3, 6, 39, 113, 92, 119, 74, 73,
170     116, 31, 127, 98, 23, 38, 18, 62],
171];
172
173/// Phi constants for M=1024.
174static PHI_M1024: [[u16; 26]; 4] = [
175    [160, 241, 185, 251, 209, 103, 90, 184, 248, 12, 111, 66, 173, 42, 157,
176     174, 104, 144, 43, 181, 250, 202, 68, 177, 170, 89],
177    [0, 182, 249, 65, 70, 141, 237, 77, 55, 12, 227, 42, 52, 243, 179, 250,
178     247, 164, 17, 31, 149, 105, 183, 153, 177, 19],
179    [0, 35, 167, 214, 84, 206, 122, 67, 147, 54, 23, 93, 20, 197, 46, 162, 101,
180     76, 78, 253, 124, 143, 63, 41, 214, 70],
181    [0, 162, 7, 31, 164, 11, 237, 125, 133, 99, 105, 17, 97, 91, 211, 128, 82,
182     115, 248, 62, 26, 140, 121, 12, 41, 249],
183];
184
185/// Phi constants for M=2048.
186static PHI_M2048: [[u16; 26]; 4] = [
187    [108, 126, 238, 481, 96, 28, 59, 225, 323, 28, 386, 305, 34, 510, 147, 199,
188     347, 391, 165, 414, 97, 158, 86, 168, 506, 489],
189    [0, 375, 436, 350, 260, 84, 318, 382, 169, 213, 67, 313, 242, 188, 1, 306,
190     397, 80, 33, 7, 447, 336, 424, 134, 152, 492],
191    [0, 219, 16, 263, 415, 403, 184, 279, 198, 307, 432, 240, 454, 294, 479,
192     289, 373, 104, 141, 270, 439, 333, 399, 14, 277, 412],
193    [0, 312, 503, 388, 48, 7, 185, 328, 254, 202, 285, 11, 168, 127, 8, 437,
194     475, 85, 419, 459, 468, 209, 311, 211, 510, 320],
195];
196
197// ── Compact H Matrices (CCSDS 131.0-B-5, Annex F) ───────────────
198
199/// Rate 1/2 base H matrix (3 layers summed mod-2, 3×5 blocks).
200#[rustfmt::skip]
201static TM_R12_H: [[[u8; 11]; 4]; 3] = [
202    [
203        [HZ   , HZ   , HI   , HZ   , HI   , 0, 0, 0, 0, 0, 0],
204        [HI   , HI   , HZ   , HI   , HP| 1, 0, 0, 0, 0, 0, 0],
205        [HI   , HP| 4, HZ   , HP| 6, HI   , 0, 0, 0, 0, 0, 0],
206        [0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0, 0, 0],
207    ], [
208        [0    , 0    , 0    , 0    , HP| 0, 0, 0, 0, 0, 0, 0],
209        [0    , 0    , 0    , 0    , HP| 2, 0, 0, 0, 0, 0, 0],
210        [0    , HP| 5, 0    , HP| 7, 0    , 0, 0, 0, 0, 0, 0],
211        [0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0, 0, 0],
212    ], [
213        [0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0, 0, 0],
214        [0    , 0    , 0    , 0    , HP| 3, 0, 0, 0, 0, 0, 0],
215        [0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0, 0, 0],
216        [0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0, 0, 0],
217    ]
218];
219
220/// Rate 2/3 base H matrix (3 layers, 3×7 blocks).
221#[rustfmt::skip]
222static TM_R23_H: [[[u8; 11]; 4]; 3] = [
223    [
224        [HZ   , HZ   , HZ   , HZ   , HI   , HZ   , HI   , 0, 0, 0, 0],
225        [HP| 8, HI   , HI   , HI   , HZ   , HI   , HP| 1, 0, 0, 0, 0],
226        [HI   , HP|11, HI   , HP| 4, HZ   , HP| 6, HI   , 0, 0, 0, 0],
227        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0],
228    ], [
229        [0    , 0    , 0    , 0    , 0    , 0    , HP| 0, 0, 0, 0, 0],
230        [HP| 9, 0    , 0    , 0    , 0    , 0    , HP| 2, 0, 0, 0, 0],
231        [0    , HP|12, 0    , HP| 5, 0    , HP| 7, 0    , 0, 0, 0, 0],
232        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0],
233    ], [
234        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0],
235        [HP|10, 0    , 0    , 0    , 0    , 0    , HP| 3, 0, 0, 0, 0],
236        [0    , HP|13, 0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0],
237        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0, 0, 0, 0],
238    ]
239];
240
241/// Rate 4/5 base H matrix (3 layers, 3×11 blocks).
242#[rustfmt::skip]
243static TM_R45_H: [[[u8; 11]; 4]; 3] = [
244    [
245        [HZ   , HZ   , HZ   , HZ   , HZ   , HZ   , HZ   , HZ   , HI   , HZ   , HI   ],
246        [HP|20, HI   , HP|14, HI   , HP| 8, HI   , HI   , HI   , HZ   , HI   , HP| 1],
247        [HI   , HP|23, HI   , HP|17, HI   , HP|11, HI   , HP| 4, HZ   , HP| 6, HI   ],
248        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    ],
249    ], [
250        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , HP| 0],
251        [HP|21, 0    , HP|15, 0    , HP| 9, 0    , 0    , 0    , 0    , 0    , HP| 2],
252        [0    , HP|24, 0    , HP|18, 0    , HP|12, 0    , HP| 5, 0    , HP| 7, 0    ],
253        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    ],
254    ], [
255        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    ],
256        [HP|22, 0    , HP|16, 0    , HP|10, 0    , 0    , 0    , 0    , 0    , HP| 3],
257        [0    , HP|25, 0    , HP|19, 0    , HP|13, 0    , 0    , 0    , 0    , 0    ],
258        [0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    , 0    ],
259    ]
260];
261
262// ── Compact Generator Matrices ───────────────────────────────────
263//
264// Each generator P has dimensions k × (n-k). It is stored as
265// (k / circulant_size) rows of ((n-k) / 64) u64 values each.
266// The circulant structure means only the first row of each
267// circulant block is stored; the rest are obtained by rotation.
268//
269// Values from CCSDS 131.0-B-5 / labrador-ldpc (MIT license).
270
271/// Compact generator for TM2048 (rate 1/2, k=1024).
272/// P is 1024×1024, circulant_size=128, 8 blocks × 16 u64/row.
273#[rustfmt::skip]
274static TM2048_G: [u64; 8 * 16] = [
275    0xCFA794F49FA5A0D8, 0x8BB31D8FCA7EA8BB, 0xA7AE7EE8A68580E3, 0xE922F9E13359B284,
276    0x91F72AE8F2D6BF78, 0x30A1F83B3CDBD463, 0xCE95C0EC1F609370, 0xD7E791C870229C1E,
277    0x71EF3FDF60E28784, 0x78934DB285DEC9DC, 0x0E95C103008B6BCD, 0xD2DAF85CAE732210,
278    0x8326EE83C1FBA56F, 0xDD15B2DDB31FE7F2, 0x3BA0BB43F83C67BD, 0xA1F6AEE46AEF4E62,
279    0x565083780CA89ACA, 0xA70CCFB4A888AE35, 0x1210FAD0EC9602CC, 0x8C96B0A86D3996A3,
280    0xC0B07FDDA73454C2, 0x5295F72BD5004E80, 0xACCF973FC30261C9, 0x90525AA0CBA006BD,
281    0x9F079F09A405F7F8, 0x7AD98429096F2A7E, 0xEB8C9B13B84C06E4, 0x2843A47689A9C528,
282    0xDAAA1A175F598DCF, 0xDBAD426CA43AD479, 0x1BA78326E75F38EB, 0x6ED09A45303A6425,
283    0x48F42033B7B9A051, 0x49DC839C90291E98, 0x9B2CEBE50A7C2C26, 0x4FC6E7D674063589,
284    0xF5B6DEAEBF72106B, 0xA9E6676564C17134, 0x6D5954558D235191, 0x50AAF88D7008E634,
285    0x1FA962FBAB864A5F, 0x867C9D6CF4E087AA, 0x5D7AA674BA4B1D8C, 0xD7AE9186F1D3B23B,
286    0x047F112791EE97B6, 0x3FB7B58FF3B94E95, 0x93BE39A6365C66B8, 0x77AD316965A72F5B,
287    0x1B58F88E49C00DC6, 0xB35855BFF228A088, 0x5C8ED47B61EEC66B, 0x5004FB6E65CBECF3,
288    0x77789998FE80925E, 0x0237F570E04C5F5B, 0xED677661EB7FC382, 0x5AB5D5D968C0808C,
289    0x2BDB828B19593F41, 0x671B8D0D41DF136C, 0xCB47553C9B3F0EA0, 0x16CC1554C35E6A7D,
290    0x97587FEA91D2098E, 0x126EA73CC78658A6, 0xADE19711208186CA, 0x95C7417A15690C45,
291    0xBE9C169D889339D9, 0x654C976A85CFD9F7, 0x47C4148E3B4712DA, 0xA3BAD1AD71873D3A,
292    0x1CD630C342C5EBB9, 0x183ADE9BEF294E8E, 0x7014C077A5F96F75, 0xBE566C866964D01C,
293    0xE72AC43A35AD2166, 0x72EBB3259B77F9BB, 0x18DA8B09194FA1F0, 0xE876A080C9D6A39F,
294    0x809B168A3D88E8E9, 0x3D995CE5232C2DC2, 0xC7CFA44A363F628A, 0x668D46C398CAF96F,
295    0xD57DBB24AE27ACA1, 0x716F8EA1B8AA1086, 0x7B7796F4A86F1FD5, 0x4C7576AD01C68953,
296    0xE75BE79902448236, 0x8F069658F7AAAFB0, 0x975F3AF795E78D25, 0x5871C71B4F4B77F6,
297    0x65CD9C359BB2A82D, 0x5353E007166BDD41, 0x2C5447314DB027B1, 0x0B130071AD0398D1,
298    0xDE19BC7A6BBCF6A0, 0xFF021AABF12920A5, 0x58BAED484AF89E29, 0xD4DBC170CEF1D369,
299    0x4C330B2D11E15B5C, 0xB3815E09605338A6, 0x75E3D1A3541E0E28, 0x4F6556D68D3C8A9E,
300    0xE5BB3B297DB62CD2, 0x907F09996967A0F4, 0xFF33AEEE2C8A4A52, 0xFCCF5C39D355C39C,
301    0x5FE5F09ABA6BCCE0, 0x2A73401E5F87EAC2, 0xD75702F4F57670DF, 0xA70B1C002F523EEA,
302    0x6CE1CE2E05D420CB, 0x867EC0166B8E53A9, 0x9DF9801A1C33058D, 0xD116A0AE7278BBB9,
303    0x4CF0B0C792DD8FDB, 0x3ECEAE6F2B7F663D, 0x106A1C296E47C14C, 0x1498B045D57DEFB5,
304    0x968F6D8C790263C3, 0x53CF307EF90C1F21, 0x66E6B632F6614E58, 0x267EF096C37718A3,
305    0x3D46E5D10E993EB6, 0xDF81518F885EDA1B, 0x6FF518FD48BB8E9D, 0xDBED4AC0F4F5EB89,
306    0xBCC64D21A65DB379, 0xABE2E4DC21F109FF, 0x2EC0CE7B5D40973D, 0x13ECF713B01C6F10,
307];
308
309/// Compact generator for TM1536 (rate 2/3, k=1024).
310/// P is 1024×512, circulant_size=64, 16 blocks × 8 u64/row.
311#[rustfmt::skip]
312static TM1536_G: [u64; 16 * 8] = [
313    0x51236781781D416A, 0xB0C8419FA21559A8, 0x5F14E1E4D88726F1, 0x762F6ED6CF32F06D,
314    0x8ABFD971E17A0BE9, 0xA5D147741B698D14, 0x2A58AB30E2BC32D3, 0x9F251FBC5DB8C768,
315    0xD73C205BBEB231CB, 0xCAB5EFF5B2C76C71, 0xFA70FAD48828355F, 0x68C6138FA5524A61,
316    0xBB20031D7AA8FE69, 0x432ADE446F49CE27, 0x5E5DB9CCCEBD1326, 0xE8782B1B01F2ABA2,
317    0x4748E9513B41147A, 0x17B1FBB78B4F914C, 0x281F5680BA56DE50, 0x74B0FB0817E33E2B,
318    0xDD166CFB774B5959, 0xAC7FDCEA4FECB5BE, 0xED747C81B540D66A, 0xB2A6A2039A87967F,
319    0x4780DCB2DC5CBFAE, 0x55BC8FF84EC89440, 0xE5D411223F09979F, 0xDDDE9D940A15A801,
320    0x194064639D254969, 0x1BE32DDC829B0032, 0x1326515A22EE88A2, 0x0EC664DD2D701891,
321    0x69748DFE6372F2EF, 0x15F3B0D400ACD68A, 0xCF4144CE1FE2581C, 0x79B1A55BA59E54AE,
322    0x65A2B47EEBAB0CF3, 0x24DD87572CB0F71D, 0xF24ABF15590F4DA6, 0x9C3BAE51969C6502,
323    0xD3A714B60B22789B, 0x3DF5504D80F54C5A, 0x9D75CF1465031211, 0x09834A0C9F659C99,
324    0xB9241BDF76EB3788, 0x6F927251C86DECF1, 0x390BE9F5BBB93D05, 0xC6F435BFA1FF96B6,
325    0x222461B658DC3E91, 0xB01DF2A2EAD2DAA6, 0x5572EE6278F6F63A, 0x17B63CB2FDA3B97F,
326    0xB233BB259F3D83F7, 0xF64760C774989384, 0x46F57E03F55B1C0B, 0x5AC8A6CEA05466C1,
327    0xAE8825521F85CA31, 0x37BEED74B5303407, 0x751FC9A15FCEE486, 0x93F0F69BD04E72A4,
328    0xC0EBFA3F49DF4DBB, 0x03E52D815DC99A1D, 0x98FE8BF01BB2CD6D, 0x009C5290D81A18F6,
329    0x4FFBAD88545CAA95, 0x0C74659FA4828CA3, 0x60CE56E32DA28B2E, 0x299D4BF82FE54B81,
330    0x51047BE3B3AE4F4B, 0xF3AC9578B9477A4C, 0x3730F81F92767E11, 0x04E84EC3A3AD1F19,
331    0x2D0E0CAB8EDD2185, 0xCEFBE8F2F538522A, 0x92DAEDC22C441893, 0xBCB999157B35619D,
332    0x069951BFB90A08E1, 0x54C7E270CBA1656E, 0x7FBBB806B6A06FB3, 0x7224943B1C3A5723,
333    0x1BAA14752EFCEBC0, 0xCFF0894975557623, 0xFA95908DC3F34D48, 0xFECA650999A26E91,
334    0x245433EBBE9CDA13, 0x5771EAFF9B02D8FC, 0xBCEBCA573D3775C8, 0x1E46F2B951D0EAAB,
335    0x32942F7F4743DDF4, 0x8FA2F60AD62095EF, 0x80E4A736B5E1A3A3, 0x0119062872DAEDF4,
336    0xE78006958CD99F95, 0xD20625057C99C7A3, 0xB569736DE2167610, 0x0E1C6183ADF09FD0,
337    0xE5C492DBB48B319A, 0xE2D83ADEFEBBDEFE, 0xAA944EEA53C77DB3, 0x0FAA85D9C13B1F73,
338    0x8ACED57F3BE4E807, 0x33CB72627624F426, 0xA0C6E669B5C74980, 0xABBAEFEA2D3B69AA,
339    0xF8366DDAE56A6DDC, 0xFDED5582F4EA6525, 0x4C9628278ED17036, 0x6E711B6D20A67966,
340    0x3B28BDF004C21B93, 0x1BC37B730FFC1786, 0x5D20C81D345FE4B9, 0x1D14A5663D369A93,
341    0x5EBD4BD39B2217D0, 0x56833BE1CDDBA6BC, 0xB288169B4E3BB726, 0xC2ED28FBFC395D1F,
342    0x035B30C68F9A6B6F, 0x539836A6E56A7B16, 0xCEB1525C6ADB65A5, 0x5F71754AA458B11A,
343    0x0DB9D180B21C0B13, 0x417D86C59DF33E49, 0x183A8F6C44DAFA24, 0x4E224C180C1F0B45,
344    0xC93CD9CA23658555, 0x7DDEC5E9451AD519, 0xB122C72A6177EE99, 0x1290B4C6B007D973,
345];
346
347/// Compact generator for TM1280 (rate 4/5, k=1024).
348/// P is 1024×256, circulant_size=32, 32 blocks × 4 u64/row.
349#[rustfmt::skip]
350static TM1280_G: [u64; 32 * 4] = [
351    0x678ECB51FE821D5C, 0xFA5F424BF55927AA, 0x3E82691332E04B0C, 0x4F88862B803432EF,
352    0x42B276259F8DA1E1, 0xF8472D1BD943D394, 0x29261575BA434C68, 0x18EF349A27CA1CC4,
353    0xEC90039764A4A063, 0x9BCEC4A6D05BA70F, 0xE7155BE17FF09CC1, 0x6E2E20597F1567E5,
354    0x5616101CEA060E2B, 0xB673068B923BDF8B, 0xB9B9343D049C63A8, 0x333E9CFE809B362D,
355    0x9D41634C404E17DA, 0x3B4161F25235992E, 0xEA4B4B8B4690BCE1, 0xF9DA36A116439BB1,
356    0x5D7254B515B4978B, 0x00D05224107BD904, 0xC85D7E580451F1A5, 0xEE9D1897913DA6F9,
357    0x42819F61343773CA, 0x11A6492A4832F43F, 0x849C11EDF0FE864F, 0xCC2704009726D66E,
358    0x89EE2A44685C1F67, 0x1DF6E416507BF2EF, 0x8759C2FB52162ABF, 0x2B61D3FB988708C4,
359    0x4A8FEA0953452354, 0xA33E2E73271E8211, 0x16DF62E503DF81F4, 0x8848BD0FF95DF357,
360    0x9BE0A7B3617256EB, 0x9A4D0BB4FE3A3A19, 0xFAA63D9E65328918, 0xD699BA354CDE6FE0,
361    0x848B1FE50AB58A6F, 0x341707F1EF36474B, 0xF623A7A5A35EC9BA, 0x24909B6E64A7A898,
362    0xBDDF3BAE7202FA26, 0x86F90C57A0399F20, 0x972B9A3187B245AE, 0xE0C5A3384959AAD9,
363    0xCF726C277B38429A, 0xBA37C244EE7717DB, 0xE45C99CA7E3E013B, 0x7B800CA46527F2E7,
364    0x75C637821CC40137, 0x51E69F16414B155F, 0xDF1964DEF13C71F7, 0x6E9E80446C5CEC86,
365    0x6F2A6DF89FF2BF82, 0xD362535524466981, 0xD5F14AC1E1C24AEA, 0xA8850D837A3C5120,
366    0xBAABADC31ECF066D, 0x76538348FC5D4D54, 0x43AD46CF3342012C, 0x63EBE2DCD832EF8E,
367    0xE6EC82F14AAFE782, 0x14D89E3823C83402, 0x8B48D6BFC823B89A, 0x68A35626E89FE121,
368    0x4BBAA33120EC16C9, 0x6ADABE06D803DA6D, 0xFCC89D41E57B10E8, 0xCC3FF0144DB74206,
369    0x503FD58652F68B91, 0x97D69DF3129C764E, 0x8B2143F7A36EF3BA, 0x7C27896C560F67B5,
370    0xD70390E698B337EA, 0x895683632A1681DF, 0x4B4E928C41EC3D9C, 0xDFD92EB2A5D5C85C,
371    0x2A5088BD76CB6810, 0xCB693D21C0E9EFD5, 0xF992506E299CE082, 0x901155A60B93AA16,
372    0x18FEFECEB0063536, 0x954870894BB31BB9, 0x66F3FD97E32B58A0, 0x2A39427A5CD8DE9F,
373    0x1A8F8616C5F7D2B2, 0x5AD2BC4EBF1E86DB, 0xACF7BFFAF3589597, 0xA777654C12DD1364,
374    0xFFC03A59DC450527, 0x33B4C871BAA2EA33, 0x93A751A6F9D72E4D, 0x69B50C7FF74151F9,
375    0x7BE8519DAF6FFAFA, 0x268DBA73A356128C, 0x0418BE2C1A43465A, 0x60C6DF650E2438A0,
376    0xEC25DC0566AEE4A8, 0xA72A030AB11FB610, 0xDD74DAF762F6D565, 0x554EAEB715F7AE6C,
377    0x5147F90AFF0EEC01, 0x12A9966C871705B1, 0xE935FF3046E32957, 0x546D69FCB8A1BD06,
378    0x6A80EA6F71A29506, 0xEF78AACF8D52B5ED, 0x9F0A496661B3B68E, 0x4B17AF965B282C2E,
379    0x7558227216E54299, 0x7D070B9CAB130157, 0x76C619D25500E2D5, 0x1F9804595D9C7F83,
380    0x6A0DDA1DF6E8B610, 0x25D0E0A1242749E0, 0xFEDA4A06072D69D6, 0x03C7DA7951AA3355,
381    0x6E9FEFF00797CBF1, 0xE936C824C9C1EAF5, 0xD4607E4688ED7B0E, 0x92E160AD731140AD,
382    0x32FEFCAF70863B75, 0x3846F110C4E23DFF, 0x79D3F753064648FA, 0x830452F5B9ED8445,
383];
384
385/// Compact generator for TM5120 (rate 4/5, k=4096).
386/// P is 4096x1024, circulant_size=128, 32 blocks x 16 u64/row.
387#[rustfmt::skip]
388static TM5120_G: [u64; 32 * 16] = [
389    0x473BC533A12C3596, 0xF642673D0DBF1142, 0x079A3868E1A6F556, 0xF0DF3DCA4493AE54,
390    0xAE4C50F12AEF6EED, 0xEA9BB30605F4A24C, 0xB0B2B4B9035331AB, 0xF53DE4752E7EDABF,
391    0xE7E08EF3E22EE7EF, 0xE645E9E59507A206, 0x52E4A2C06270B2D1, 0xA418134BC0D58678,
392    0x0A84E53303F4092D, 0xB47056AD3C0847AD, 0x2DEF73813B17101E, 0x79A3A58A7E91C4E2,
393    0x667AA815610234DB, 0xA0FFA951CABB8BA7, 0xA3271642E4BCDD24, 0xF8D89BD783317ABB,
394    0xCC64FA95F06AE45C, 0x7E38935D78BF5F80, 0x510CE9ABC6156F00, 0x8B317C79E0122B09,
395    0x3CB09E20016A5F93, 0xE207C144E889F3B9, 0xAE6185E4345C5971, 0xE03AD499EF850D33,
396    0xFA8B392CE78B5712, 0x290CB2F518F3E0CC, 0x429C39F0915EB60C, 0xA0545B6AB2967149,
397    0xFE9FF6C26898CB92, 0x6F9BCD129AA52083, 0x3FC159DB58B64D39, 0xCB27847434F177E2,
398    0xE040D71365D96A1D, 0x54FD20051D3A50E7, 0xE8AC736B6D2BB546, 0x8FBF68DDF5789C2F,
399    0x4954E4153CFF0F52, 0xF8F8F5B243A03E2B, 0x99A1DDD23204D103, 0xE323158E0FEE7673,
400    0x43C2A07046BA1B43, 0x07BA6CEC7D740CFE, 0xCB4E113F94C6CAA4, 0x652EFD867B43D199,
401    0x081E779BF01F34C9, 0x7337A3ABC8698644, 0x9C9E794155E27547, 0x283C1AB2706A388D,
402    0xFB9DFD194731EC2A, 0xE99EA6B641B309A2, 0x258D45A1BBEAFFC7, 0x87E61289A54A2473,
403    0xFDF3E96C7679E979, 0x911C4BE65A333250, 0x178259F846AA9557, 0x7C2EC448EE709423,
404    0xA61BE7CCED034296, 0x5CA234AF02914916, 0xE045B3C585714F27, 0x2D40C8085AE5E8F4,
405    0x7FB352B26E544BDC, 0x18D76B323C3CE1BB, 0x8421967EE08A6F71, 0x9B675F06F13FF05B,
406    0x672C29DC5B80E18E, 0x2F4C42D0F6D5D6D4, 0x7DE072F73A801586, 0x2A275B2CEA2FFC1C,
407    0x284B87ABA22362D9, 0x8952442BBDFBF4A3, 0x2B798BCD5D8C0B02, 0xBBE5DE4A96569F99,
408    0x409E72F4138595F8, 0xB3C14074BD8E33E0, 0x3B07838358BBAE63, 0x1C8258D6B07D2E1C,
409    0x403149A1C88E4D48, 0x93FE719B2638B7FF, 0x9886F3E90FC01869, 0x9F3B39183F2219DC,
410    0xF5B0D3AA45122586, 0x7913FF8FF979BBE0, 0x795DFCBCC98210C0, 0x28FD21380EBDDABF,
411    0x0BBE0D91FA504DC4, 0xDC8848AEA001577F, 0x51653E755F6CB4F7, 0x5ACE347EC899304D,
412    0x1D0EE239D8A6C2E2, 0xEA13D4CFB3394FCA, 0xBF707E3ACD882B91, 0xFDDD44A7EA0D1F3D,
413    0x14EB386A5A452498, 0x3682993353F8D76E, 0xF9850534D2FB4F19, 0xF787897435C5EB0F,
414    0xB680840F8D34A099, 0x5BA0A94E309A9194, 0x6C66CAA0567BFFD6, 0x09B6484BCD477702,
415    0xB62A4053A6916719, 0x693D50608EC1D717, 0x23C38E6F64963EE8, 0x36ADC6BBF39F4CD1,
416    0xA40947C16AEAD43F, 0x621457BDB766A157, 0xDD6118ACF503356D, 0x0B3479828C296016,
417    0xAAB1061EC9FA6BA2, 0x1E81D7E22D3A7ED2, 0xF902B6C336258F5B, 0x6B54628AC96116DE,
418    0x5968E3167BB1E221, 0x714B0F4B3B9D7E0A, 0xF12374361559D0F0, 0xE0C7FCC959B1A9D8,
419    0xC103B779B3A769AA, 0x8D955160E4B9F9B7, 0x231B28E0B7490C8E, 0xB883F29AF6CC4F12,
420    0xA7D1FA32F82AAF12, 0x8FBC6AC53532AB89, 0x17AC06392CDAC681, 0x817D2F5475016296,
421    0x434D8612F27169A4, 0x9ED244393B87DB5E, 0xB66D806A5A9ADF46, 0xD83C7DCFDB4B72CA,
422    0xA78E0C64307885C6, 0xE67C870BD21EC431, 0x11B79B0BB0B977D9, 0x792535C16AA7D982,
423    0xB597FD60982B8C42, 0xD019390EFA14B3D5, 0xC57FF5CFA1C438AC, 0x576782A5B48B78AA,
424    0xAE278E95DA048F72, 0x0B7DB5FB6488287B, 0x893C7E7E8DCB6E5E, 0xD5DB819D8901B32C,
425    0xB7BA8906FC3AEADE, 0x22254872ECA99117, 0x74F39404FA2779F4, 0xC55D649E5A6AA628,
426    0x4A1F8910EBF76F2F, 0x4E3EF686266CEBB8, 0x8363A57CF1377C68, 0x419BEFE6C848FEDA,
427    0x8F141154BFA88D31, 0x446EF367ED965F98, 0x1242B3F840426E98, 0x010B84A957090390,
428    0x9CE9E0B619E61C4A, 0x481F1DD44360BCAC, 0x0938AE511B2B47A4, 0x2F5F59FBF547D991,
429    0x85B68FFC07A32A49, 0x5D9A708FAECD2C41, 0x69CFDFFD21D6B2CF, 0x3F91CF5820823B83,
430    0x7D62406050908C82, 0xC21CF32B862166F2, 0x82AF2DF8E6CADB5D, 0x043FBF863ACE6599,
431    0x700097EE5FDDD825, 0x468C544985C983CE, 0x69EE0178288A8E1A, 0x12009EBF2E4382DE,
432    0x2B8D59DE631991AE, 0x1B67C70786B43BE2, 0x860FC3354C9FE425, 0x3EBF307D1C643E22,
433    0x905330D76B163401, 0x20BB399A08061CBE, 0x9D5765CE993D7092, 0xA8150DE46D6CA810,
434    0xE03534D4DA2B66A0, 0xBF2AEF3B833E18DF, 0x6C1C0D9EAB1E26FD, 0x2481F6BB6AB674C6,
435    0xD98BD8D3FC0E0557, 0x352CF52EEA654A92, 0x0DF8D4B0FD41AD3E, 0xE547119C2446F840,
436    0x4C1F458D1E2F4B70, 0xD9023F0DFC06EFE9, 0x24349C5D9DE2B048, 0xDC74D3E888043526,
437    0xE864E5EE002EB3B4, 0xC31A8D3B3E22D2C6, 0xB3C4136542237F8E, 0x3C75AA228AB1B2F5,
438    0x43DF20DF407EAC80, 0xCAF22FDDADD586C9, 0x9414219FF8074265, 0x2531AC5CC0E52866,
439    0x1A68E6BC5CA7FCA3, 0x86396D0F56A2E7A3, 0xD9EC25B8DEA08EDB, 0x6A9E6CFFEC7B15C1,
440    0xCD48176480B2E0FE, 0xD349142BE9888043, 0x9A70BAD89B53A446, 0x1301DF6C1763EB67,
441    0x5C9B0F852875D4B0, 0x6EFA7FF418710592, 0x6F7C0712083341F6, 0xA97F398A275243DC,
442    0x3D046D9B0B0B6AB3, 0xFEB99F72A70BAF35, 0x50F7B484C2530BEF, 0x63537B68EBDCF01C,
443    0x672E8B1DD9564310, 0x36302F8557CBB4E0, 0xC9CAD206AB0AD88C, 0x655E0F52C70AEEA1,
444    0xFF7EC97F9439C9D4, 0xCD71487F10065DE0, 0x532339617D706AEF, 0xA50A23B90B57978C,
445    0xB7E0C9A5F3EF66B9, 0xABA49150144FCBEF, 0x2C9E63DC18BE8ADD, 0xA0FD7E7E8F7FC5FE,
446    0x5C55C60E14C3D7AC, 0x4D00D9F6C827E1EC, 0x4E40D57E1740089D, 0xB1248707D195C038,
447    0x4500AD976DD321E6, 0x133113D244711330, 0x0260379D0A20D10A, 0x899019157631007D,
448    0x4DF741A808694A99, 0x56E493B4668B67FD, 0xF89442CABAA2262C, 0x398171D62E938504,
449    0xCCF8A4E13D655D55, 0x91DC40D2C6607CEF, 0x353E539A020B0C60, 0x8F843A855BA9B7AE,
450    0xCD31CCCB9388FECD, 0xEBEE1CCF42943E77, 0x9CA39E64D8AC9E23, 0xF15A0CB4C73ACB80,
451    0x3BF0F0DA9576923D, 0x95089979081ACA77, 0x359B090725B62278, 0xF00D0222CAD4C0FF,
452    0x4ABA29056D55C5AA, 0xD990AA10A9A1A9B2, 0x27A09750826682C1, 0x57BD7CD2178FDC96,
453    0xAFC3076AF8AFB82B, 0x45FE8F2628F489F1, 0x2CFA95663A96A30F, 0xB3831F756D9E666A,
454    0x011EE24F6C5EE283, 0xC3EE09A1D5FAF1B9, 0x7B49CB7B94EDEB20, 0x7221A9436E1FFDF5,
455    0x5D36302EEBDD74AD, 0x27158F4D9DF0FA6E, 0x497015959B333E79, 0x885FBE22B9B72707,
456    0xE330EEAD520B31BA, 0xD1A5DC55EF54193A, 0xD6C112F89677E27A, 0x26F1DC62E08DF49C,
457    0x2DF5B0291E619A18, 0xD802502086037C46, 0x730D20AE9364A6AD, 0x090B789D8AA6C6CC,
458    0xEA476A585503E90B, 0xCAAD943DD30E1BCC, 0x1D5C236ED01E9E5C, 0x8E94E96FA7252ABF,
459    0x3EB2DB84FB4837EA, 0x5153CA825D11F86B, 0x574E63C92DD0E75A, 0xD8DDFF2B37CC97C9,
460    0x5E83299E60C44293, 0xBF0824C62EB7980C, 0x5678B852002834EB, 0x2D630EAC536FFB78,
461    0x9A41F048C1C68187, 0x734BFB916EC3BFAF, 0x4B23BDA1162B30CB, 0x7AEA9F03BEBCF597,
462    0xC65460BFAF9C8913, 0x608F9888E738F4A1, 0x017AEE470FCA60F9, 0x711E9BE5EB98E7C9,
463    0x4EE8869A59EDF8BD, 0xD52C5B5388B35249, 0x8EB0D25B439273CA, 0x6545E82E69D8677C,
464    0x5B23991A53041EA4, 0xB276405C156A9DE5, 0xA90889BC74530A5F, 0x87CCF024E591E18F,
465    0x22735E1E720A8B3C, 0x29A80F3696D6F157, 0xF68ED2F2389D5D2C, 0xDC59D706495D815F,
466    0xD0EE25B73218D571, 0x7572387BFA03A7C2, 0xA0717B27763FE223, 0xBDA3EB0DAFBEF276,
467    0x9DBB8235D11298BE, 0xE28B39772ED91A35, 0x92DE6FED2F6766E0, 0x1DBA188153DEA205,
468    0x48930E9A21873E62, 0x863CA15D6DB058D9, 0x61A29088FE3983D0, 0xE1699EF0AAFA5FD1,
469    0xA730056900988893, 0x82252873E627D6FB, 0x7862DE8A3D0F1A93, 0x87963F38A82E4703,
470    0x78BAB9252EE72FB0, 0xC798C7C684B6E789, 0xB7480D9712BFA72D, 0x122F243674AD887F,
471    0xEC1851EB80A37133, 0xB68F0F709DB32E05, 0xA809CB3638414FD6, 0xE156821BDAC256E0,
472    0xB75342B6CFF7ED42, 0x8521AB48A4C55D66, 0xC9AB047D79A48428, 0x9C820E8FADD87251,
473    0xA69C02525644F41D, 0x03197EF26112D606, 0x3DF71AD0410035AE, 0x1AE7B0AB310B6967,
474    0xC4F82E31B4D9B491, 0xEF8E4992FDBA61B0, 0xB6B367CDE8DE0CAE, 0x22875F641288E733,
475    0x5C142A9C7C2E259B, 0xD38D66117E9E861C, 0xD27BF85E8EEE1920, 0xB57D0C62B512E2D6,
476    0x68B4500340B7B92E, 0xDD05A44D36AC1651, 0x4E77C4ABE92FE174, 0xB5D9F79070685288,
477    0xA22B2A6C9A75D7A6, 0xEEA5A0DF8A4950E2, 0x24C4830123FAE1EB, 0x6EB0AC9C2D8C508E,
478    0x1BB99D6785EBCCDD, 0x9CD6A50CF53CCA00, 0x0624E36FD0817F2E, 0x198340098E60DFBF,
479    0xA4EB92DD48085594, 0xC6F755C563F35020, 0x04BDFF9A2309C6E6, 0x73CE08D94A45BBC4,
480    0x8B8EC43906C28869, 0xAD4E41FB147A7696, 0x8AB66E9B68FA00BE, 0xF90D3E078D0C6FFC,
481    0x89A79E9CF0BE90A3, 0xD86305B6491A49B9, 0x222A27A68236765A, 0xB32D41B1E0616C83,
482    0x99931668E57EB637, 0x8C8F4ED1C27BEDD3, 0x35166846D0C673B9, 0xA8D2184C1901433A,
483    0x4D768A5E0109B5CB, 0xC198869334D81C43, 0x2C6A48CC47FD21F9, 0x608107FF80FE37AA,
484    0x4DD3A7395630BE4B, 0x64F776C5FC6B2C31, 0x4DC16B1E2B2A7F6E, 0x0E9FDAE3B60F8FAA,
485    0xCFA794F49FA5A0D8, 0x8BB31D8FCA7EA8BB, 0xA7AE7EE8A68580E3, 0xE922F9E13359B284,
486    0x91F72AE8F2D6BF78, 0x30A1F83B3CDBD463, 0xCE95C0EC1F609370, 0xD7E791C870229C1E,
487    0x71EF3FDF60E28784, 0x78934DB285DEC9DC, 0x0E95C103008B6BCD, 0xD2DAF85CAE732210,
488    0x8326EE83C1FBA56F, 0xDD15B2DDB31FE7F2, 0x3BA0BB43F83C67BD, 0xA1F6AEE46AEF4E62,
489    0x565083780CA89ACA, 0xA70CCFB4A888AE35, 0x1210FAD0EC9602CC, 0x8C96B0A86D3996A3,
490    0xC0B07FDDA73454C2, 0x5295F72BD5004E80, 0xACCF973FC30261C9, 0x90525AA0CBA006BD,
491    0x9F079F09A405F7F8, 0x7AD98429096F2A7E, 0xEB8C9B13B84C06E4, 0x2843A47689A9C528,
492    0xDAAA1A175F598DCF, 0xDBAD426CA43AD479, 0x1BA78326E75F38EB, 0x6ED09A45303A6425,
493    0x48F42033B7B9A051, 0x49DC839C90291E98, 0x9B2CEBE50A7C2C26, 0x4FC6E7D674063589,
494    0xF5B6DEAEBF72106B, 0xA9E6676564C17134, 0x6D5954558D235191, 0x50AAF88D7008E634,
495    0x1FA962FBAB864A5F, 0x867C9D6CF4E087AA, 0x5D7AA674BA4B1D8C, 0xD7AE9186F1D3B23B,
496    0x047F112791EE97B6, 0x3FB7B58FF3B94E95, 0x93BE39A6365C66B8, 0x77AD316965A72F5B,
497    0x1B58F88E49C00DC6, 0xB35855BFF228A088, 0x5C8ED47B61EEC66B, 0x5004FB6E65CBECF3,
498    0x77789998FE80925E, 0x0237F570E04C5F5B, 0xED677661EB7FC382, 0x5AB5D5D968C0808C,
499    0x2BDB828B19593F41, 0x671B8D0D41DF136C, 0xCB47553C9B3F0EA0, 0x16CC1554C35E6A7D,
500    0x97587FEA91D2098E, 0x126EA73CC78658A6, 0xADE19711208186CA, 0x95C7417A15690C45,
501    0xBE9C169D889339D9, 0x654C976A85CFD9F7, 0x47C4148E3B4712DA, 0xA3BAD1AD71873D3A,
502    0x1CD630C342C5EBB9, 0x183ADE9BEF294E8E, 0x7014C077A5F96F75, 0xBE566C866964D01C,
503    0xE72AC43A35AD2166, 0x72EBB3259B77F9BB, 0x18DA8B09194FA1F0, 0xE876A080C9D6A39F,
504    0x809B168A3D88E8E9, 0x3D995CE5232C2DC2, 0xC7CFA44A363F628A, 0x668D46C398CAF96F,
505    0xD57DBB24AE27ACA1, 0x716F8EA1B8AA1086, 0x7B7796F4A86F1FD5, 0x4C7576AD01C68953,
506    0xE75BE79902448236, 0x8F069658F7AAAFB0, 0x975F3AF795E78D25, 0x5871C71B4F4B77F6,
507    0x65CD9C359BB2A82D, 0x5353E007166BDD41, 0x2C5447314DB027B1, 0x0B130071AD0398D1,
508    0xDE19BC7A6BBCF6A0, 0xFF021AABF12920A5, 0x58BAED484AF89E29, 0xD4DBC170CEF1D369,
509    0x4C330B2D11E15B5C, 0xB3815E09605338A6, 0x75E3D1A3541E0E28, 0x4F6556D68D3C8A9E,
510    0xE5BB3B297DB62CD2, 0x907F09996967A0F4, 0xFF33AEEE2C8A4A52, 0xFCCF5C39D355C39C,
511    0x5FE5F09ABA6BCCE0, 0x2A73401E5F87EAC2, 0xD75702F4F57670DF, 0xA70B1C002F523EEA,
512    0x6CE1CE2E05D420CB, 0x867EC0166B8E53A9, 0x9DF9801A1C33058D, 0xD116A0AE7278BBB9,
513    0x4CF0B0C792DD8FDB, 0x3ECEAE6F2B7F663D, 0x106A1C296E47C14C, 0x1498B045D57DEFB5,
514    0x968F6D8C790263C3, 0x53CF307EF90C1F21, 0x66E6B632F6614E58, 0x267EF096C37718A3,
515    0x3D46E5D10E993EB6, 0xDF81518F885EDA1B, 0x6FF518FD48BB8E9D, 0xDBED4AC0F4F5EB89,
516    0xBCC64D21A65DB379, 0xABE2E4DC21F109FF, 0x2EC0CE7B5D40973D, 0x13ECF713B01C6F10,
517];
518
519/// Compact generator for TM6144 (rate 2/3, k=4096).
520/// P is 4096x2048, circulant_size=256, 16 blocks x 32 u64/row.
521#[rustfmt::skip]
522static TM6144_G: [u64; 16 * 32] = [
523    0x80924F648C014F2C, 0x73889C8B87D0491F, 0xA9FA060D2902D7AC, 0xC8B679CF61EEB5D9,
524    0x6BB9E90F5C157AA1, 0xBF03EF756245D917, 0x9063F2CD999EF1E7, 0xF7925B3FB7AC7B2D,
525    0x6CD39516B201F491, 0xE2BDCA4E34542B5A, 0xF3703B3C8EE753FB, 0xE998E87323F0B228,
526    0xD1F551B2D7E7822F, 0x201E24066584D63C, 0xAA00E8DB909EB41C, 0x4157EBA0F5C76A50,
527    0xF7C5731746C6DAC2, 0x60A345189009C0B2, 0x3372F1E9E0C5A079, 0xD00B09158E164B22,
528    0x33D5F8A268041CAB, 0x66317898CD0024E3, 0x106EED5C2171B3F6, 0x276B8EA59AA981E0,
529    0x010BFF3F52A49ED9, 0xA6FA7F151FCC72B2, 0xAF3BD932065043F7, 0x447B4D0FC4A2B93B,
530    0xF8D345E6D2B0008D, 0x1B363BFE296B55AF, 0x38E3E16EC5856A12, 0x2E4931CB3F2424B1,
531    0xA099B776C642FF1D, 0x84B0DB797098E17E, 0x75FE9BB5CF7FA873, 0x9711A89660DAF24D,
532    0x3CA8DE5500F68DB4, 0x49BFF74251B24E46, 0x91EAF386C81014C9, 0x1AC700298E095F0B,
533    0x12CEE8B5F6B93C11, 0xAD628CB6CB81F76B, 0xE095C2C994A8BDDB, 0x4E2C48C942B4D481,
534    0x1F7E191B30E8FFD6, 0xD4A7E9BEF81BBB0A, 0xE6608F647B1AED9C, 0xCA7FEC5498C03F0F,
535    0x1132E816BDFA0C34, 0x50C3993911E10EB1, 0x097CD7A1F32C54C8, 0xB009654E56B25A2D,
536    0x5FD58EEAED460CEF, 0xC18E2FBAD2954467, 0xE32118F01D05456D, 0xEA2926A1E761DF76,
537    0x4C6C7BF3A2245C1B, 0x4630775DC59EA74A, 0x14EBCD8B5D72E343, 0xBC6F7FEA452F2CC2,
538    0xC09CE802B35EBF46, 0xD1F3069957DF1D15, 0x2377F45ADF614CC0, 0xF5DAB8FCF394CCD0,
539    0xFEFBA8CE169FD377, 0x5B2280EF3BD870FD, 0xDF7CB95F2943D0EE, 0xA84529FF0D1B1C19,
540    0x0CA5DB06A87541C8, 0x1BEF913D5145F20E, 0xFAD861F673B32028, 0xB4713377C056CE97,
541    0xCA3F213365EE380F, 0x7E90466945BDE9F4, 0x4087C8C73A7CC5F9, 0xDE71B7683D018D86,
542    0xA6CDFD8D8117748A, 0x4B41C3F5A6676549, 0x5711EDC02F9581F3, 0xE7C2E0FD9004B03B,
543    0x77D0EF5DE2ACACA2, 0xA4371A5B111B877D, 0x0EDDF83C3341A5AA, 0x51261FA4B5A0D7EA,
544    0x7C563512A6B73B3B, 0x43F8D1D113D751D6, 0xB2CABBC350FF0F8C, 0x29361DCE5EB87C8F,
545    0xF6DFA5C672C25179, 0x31371ACB6462A596, 0xD41419CD4F0F84EF, 0xF98DCBBE610AE03E,
546    0x05FF840FB320DD5C, 0x3FB4FE4A58585109, 0x14A5161B2AD3C3E7, 0xFD02358505190F0F,
547    0x5B6D534EDE13068A, 0x2459CB07007121B0, 0xF07B08B8227047C1, 0xA629DCA5A4E30D28,
548    0x5D00E72E5B6AD57A, 0x9F0F9E0608702BDE, 0x8BDBFA371C06D96B, 0xFE0E603775A875CB,
549    0x692EB7DA76BD0D4A, 0xFE92FCB5B5184BAA, 0x3EEE37900144CA03, 0xB7A22EADE2F061FF,
550    0xB3CDE2464AF12129, 0x79A99380340974A9, 0xF85478E5A2E8B907, 0xE74EEFA4CB7625E5,
551    0x41AF736E0AA1416E, 0xA676E43CF5DFF372, 0xCFFC30D6C0A58A33, 0x3268136A3020033F,
552    0xF50111382FEBA594, 0xC255896AB59C0663, 0x8406956F19B67F80, 0xA3A7276060D4E7F6,
553    0xDCB75287BE9A2620, 0xA1F594570B269097, 0xA51A32548BAA6DD9, 0xB429B8AAF992C8C0,
554    0x6210A36B63DE9C73, 0x2339DC1AFA94CAB4, 0x75574A6D1C4D0C17, 0xF148B8AD12816B47,
555    0xE24D7C17BCC46297, 0xEDC41AA9B5C9D936, 0x89843027C6A78449, 0xF8D151E1F42BE98F,
556    0x4544BD9E6975DDD4, 0xBC9B3EFAD50AFC58, 0x2CAE269677B130FE, 0xD2C39D5EBDEE56B8,
557    0x6A13BB53C03B0C8A, 0x4E0D1697322A1A30, 0x55054229A69B6CCB, 0x7E1FB0B885B90CD2,
558    0xBE5C66B252E5C51D, 0x7D9E9E25922566C1, 0x8F0234F2A330041A, 0xEC6A4F2729A2A30B,
559    0x1E04A65CF0BA05C6, 0x2B15FEF9967ECD97, 0x5EC43C035DE4EE64, 0x22237F56834AC746,
560    0x4FD0C1AF8A61F566, 0x86326F93EF63E2C1, 0x14D55726A5F74BFD, 0x99AE7713DF2DE6CF,
561    0xA9CC4B50995A682C, 0x6F6F12C80929FF20, 0x8C72007D6A253FD3, 0x6DE363E8EBF2B614,
562    0x95F6F59DA4CE4BA4, 0xD6D4D371A2484F16, 0xEFA33CD34F71B817, 0x02F0E99C031B089D,
563    0xE16A7B75AB838252, 0xD1840EF2935AA1CC, 0xA5C8470F98202BAB, 0xA93EEACE43EE56E1,
564    0xB2D767F35B0F34FC, 0xE855B53B6B8DB8DD, 0x08BCF47684E904FA, 0x47965D72107897D1,
565    0x3D38403A0D2696A7, 0x67679C6F9CC37537, 0xA93A125CE7041EC4, 0xF39AD7452597ED13,
566    0xA0CCD841B7CA93DB, 0x6F7039B929A820F5, 0x5A95AA3786C96E04, 0x34DA46A084653B1A,
567    0x08A907831A27892D, 0x0DD5B6C9FCB5229C, 0x0C03663794A4E94E, 0x3FB22E4068ED0EE8,
568    0x53BCBD15AA8DEC34, 0x51CEF53541B04056, 0xE4DCA0393836E9B6, 0xDFCF9B01E901D933,
569    0xBD160166307B70BE, 0x5618C6E0B4ADEBA4, 0x6F65C69080D4C3FA, 0xADF1AA22911C2C69,
570    0x42FB1575074655AB, 0xD1EFF5784CBE7FA0, 0xB110981C8A0BDF01, 0xC650189C2DC9FC74,
571    0xB403563011DDE16F, 0x92630CF312B3F7F4, 0x95E74B3B582DFB94, 0x01F509A35BD2528C,
572    0xA81600F6437FBD00, 0xFCF0E4AD41DE3598, 0x434EE3903CD1A17C, 0xF618E8E2A47EBC4C,
573    0xA1D7816AE33BA46E, 0x3A9D5B3CBDACF93D, 0x538802ED0FCCEFF1, 0x93DB9D6B79C7E508,
574    0x54B42DDFAA7DE9B5, 0x299F4C1B5DA05487, 0x562D20349282F706, 0x1E3159E4EAB09D03,
575    0xE15D45F2D1694FF3, 0xFF1AA1FC1E58E3FB, 0xD6875B71B982AD57, 0xAC96CD3B7BE8ACC6,
576    0x90CADDAD41374E4B, 0xCA29AAB22CAD6198, 0x9158C474E0725B4C, 0x4C5442D6A12D94D8,
577    0x2827752CE49CB9C3, 0x85AD35C129110989, 0x2EF85A7A6C043BD8, 0xE3BA4AC3D5146FB7,
578    0x87002794AC4020B7, 0xD229EAE70E01E72F, 0x1772B0DA401ABE2C, 0x2D487EF60724DC83,
579    0x413A0F58974C76AB, 0x4C17AB24F37CB105, 0x5FC1827A1DDB0456, 0xCCAA7F9477CA64FC,
580    0x904E1D9338D0795C, 0x6844F79ED8B26A9D, 0x306F66975CE704A9, 0x25E72EC95509188B,
581    0x2B5EC3212ADF3595, 0x4F1CDA9CB6CCC28E, 0x422F23AF81659F6E, 0x4AFDD03EFB8AD730,
582    0x84D1CCA3B5036F03, 0x1EEDE0F1121E6F62, 0xD232DFB74A0582EB, 0x3303D1E98810A6C9,
583    0x221F0EFCA2C81259, 0xB57F8E6943D0CD36, 0x088A64DA7FE2E6E7, 0xE0F63EAF873B8A79,
584    0x57E9B39245C61730, 0x88B024F34ED7B64F, 0x8784413FF95E4764, 0x74FECDAE7BD62E5A,
585    0x807A807832F6AC83, 0xBC7CA7F754BBC7DE, 0x72CCC85425068F50, 0xED52419643561832,
586    0x1B9CF54C055FB01B, 0x40740A0D46985529, 0x2AE8A0C58756BDD3, 0xC6DABE268551FD5F,
587    0xDD8CE660B7403DC8, 0x672EA620E65301B0, 0x865A23FE568C1736, 0x69EE1D7F7A1BD748,
588    0x3CCFAC84AB188D90, 0x6D70525D092C3E2B, 0x46C6675C1CF4B30A, 0xB346022E43DA20B8,
589    0xA01DC1159652EA26, 0x0B411971B0E3D039, 0x3C1E75AB0EA462E1, 0xD07D0847EFA9CFBA,
590    0x4153E6B4F4687D43, 0x4414BAA200FA38CE, 0x46B28D3B4055C633, 0xAAD0ED2FACD6B415,
591    0x5234FA7B72F478A1, 0x93EC14698C611F3C, 0xB70BF72C15E0DCE9, 0xCC048A526AC1F46A,
592    0x969C10820390DF8D, 0x90AD0138202A3218, 0x2398B70405520538, 0xD08C1F799FBC0755,
593    0x53D8304A8B5213FF, 0x88DD1620B1A5125A, 0xF1CC9A07F95C61C5, 0xC6C625F64FFCDBE6,
594    0xED1E06EC959FF323, 0xFD3E8AF3553D90BD, 0x529D699B08B873F1, 0x64F59B1CD522AC0F,
595    0xA5C8A02849509DEC, 0xECFADD4C89C03A78, 0xE1564A548D89DECD, 0x90DDBCAC7964E9F0,
596    0x545B207877BBAFB5, 0xDED6AEAD3967CA72, 0x272E128C97B06868, 0xFD3BB85996640432,
597    0x2995ED49B525D47C, 0xE868EFD6FDBB0BB6, 0x975DC82C8580D00A, 0xBCB9FFC6F532A0CB,
598    0x9F0B1EC3BC16C2E7, 0xC94F5149D03677AD, 0x039452180B24DA43, 0x4F5BBAA0BCEE64ED,
599    0x910009CE6C11178F, 0x5BC794754EBA7200, 0x3E9A53CDA988B33C, 0xE2D0A0965DAACA23,
600    0xBF8A7AE5330F4813, 0xAE7F8E4F25666EAB, 0x3F0351BD34ABBFA8, 0x874D88D5FC4E9385,
601    0x45A0C20F7DFD3928, 0x72ABDCB19E4F6F09, 0x7044266B9EA6F0B3, 0x18A5011D0E51E735,
602    0xEE58F5FC44AE8595, 0x64B64F3D173C58FA, 0xE938AFB934CBB972, 0x45F7B1A1DDD4C559,
603    0xC7DF1E821B249BE3, 0x5E6CAB842F3DFCD0, 0x141E428141C28BDC, 0xF54B0985329F6E2A,
604    0xD8C083075232BDEA, 0xDEA797B6C9E15606, 0xA72B8B48502B1C04, 0x4BA89A8DBC54EB6E,
605    0x718EF66E726EA72E, 0x631B9B22E193F012, 0xF3FB2D112468B0DB, 0x89F0C3C8A143E9B1,
606    0x7D6BE8EA6A522A10, 0xF46EC5A56E3F5725, 0x86884547536AFFAD, 0x0C82A42D88AAA64B,
607    0x0B740E17EEF10A80, 0x0DE1916C291C1535, 0x845114313E908D31, 0x3B58018EB77DED61,
608    0x9A5F7429731308EF, 0xAB68D1725D8F9501, 0x234F9035869415A6, 0x2262095D77A9613A,
609    0x9BDCBC26ABDE4672, 0xBE5F130E1089BE8B, 0xF5CA0ED3FCD9F28B, 0x75CC07E9822AA2EF,
610    0x6AC735D6621C86CE, 0xA203E9E1FC993207, 0xEDC164396C7C8FF2, 0x27F92979A313914D,
611    0x8E1D4E308C03F66D, 0x73D76A715F859BED, 0xBC8D709D4BEFC155, 0x8D74B49860A90ABA,
612    0xB67C75041BFB3A61, 0xBBBB73DE2B3D7BB5, 0xCB254F10257495E3, 0x185C71C3559D9CD0,
613    0xACB7A163EB1E0886, 0x24F946909B29B2C7, 0x373C5CF4F6B1F3A7, 0x5DC49B1574B3AAB8,
614    0x327C55142CE3D138, 0x2EA917A7C6730E01, 0xBA6BA43767D53E84, 0xFFB7D61D6EAD24AD,
615    0xCFAAC26024A1D642, 0xC795400B8646533A, 0x435A4FE899704FAF, 0xAE2BF452BD9AF093,
616    0x53759538B5F4A861, 0x4F1AB4840CFC1EFD, 0x8CAFCB067C991FDF, 0x2658ABA23F8B0B93,
617    0x6B3A35CDECD26C58, 0xB9F1318AF46F1376, 0x7758FC0F74B7DD05, 0x0A9B1A1C7F98B930,
618    0x4B4C20D040F3A8C7, 0x46453ECE10C0A1F4, 0xF74BDDB1A8FCFE1D, 0xE2C19148A5E88F1C,
619    0xA98B4DE68DDB2434, 0x893BEF8F2CF8DB58, 0x4CEE8F0E39D30CD4, 0xC87017E7EE6886F8,
620    0x23024E83F777D7DF, 0x0D7E46A8B5F9B133, 0x1D0BC2F79BF5559C, 0x3241D5BDC7E7A665,
621    0x9E1DD50373C16CC9, 0x7A5E390921B471EF, 0x5B39731CCC2CBDD0, 0x8876080680F9D974,
622    0x9DF22EE3AB758F85, 0xFD490012FCFF20B3, 0x329A5648D2585903, 0x6C0586C65F46236C,
623    0xB009BA2650ABAFC4, 0x5653D61D2BFA255D, 0xE767D0B25AC7736E, 0x8E5200D21EE3E28F,
624    0xFD96F63D0A22CD57, 0x4ED61899ECDEB4BE, 0xB333F994AC7791FF, 0x89EC600B857D4DDD,
625    0xC2773C7DCE36709F, 0x70180CFFAE22AD44, 0xA4A20211224F8ECF, 0xB336A54A681A1F59,
626    0x5C00C419C78A79AD, 0xA49562EFB784ECE4, 0x4BAF45C1E75BD84D, 0xE7C1C69100F8B93A,
627    0xDAB0C7C65F0D0963, 0x51BF8A0EE9CEF5F7, 0x756A9A47B4EE8042, 0x0DEFA16B0E74CF18,
628    0x0FAB86E762595261, 0x852E38F9D797D4F7, 0x96DA18169AFAC99E, 0x8235D4DD6C2BB887,
629    0x15D0F65E9ADB2C67, 0xA887E5D8EF4E1080, 0xAC968F4C0D673CA7, 0xA74759A7F1B4E383,
630    0x1B5641CE5FADE005, 0xEB947BE5E20E7DDA, 0xF6372655825B3516, 0xF2EC5B36D687895F,
631    0x2C0BB35E3C3EDA32, 0xC19BFF6F3A2397A8, 0xE25C646059359D90, 0xA1372FCAEE250A43,
632    0x8AABBF162C4499F2, 0xFECFA27F8D7582FB, 0x607B88D04F4A6100, 0xA3D2F8A88A2E5E80,
633    0xD9C26C2A023943BC, 0x62F3C18658A0F5C6, 0x4130BFF0D74BBB85, 0xEBFFFE197C94C6EC,
634    0x0AED385393F69FA9, 0xF7E69DDC061B85E4, 0xE77D0BE2013061E9, 0x4A0DB8AC2995096F,
635    0x775369B59AA940DA, 0x96B47429C339536B, 0x51ECC59C60BAD762, 0xFA275A6A8F90885A,
636    0x922A84AE2B06B400, 0x3C0A7BE22FB21136, 0x5376C3FBFC03EB0D, 0xEA264F6769B57EE2,
637    0xE518ED3DD8553DC8, 0x815E57F23DADC1A3, 0xE99030AA02A35296, 0x04EE4BD66D770F8E,
638    0x8AB3C94077F85772, 0x647897A76CFE4EC5, 0x6FCAA7A28968065C, 0xC73BDD88ADA4D60C,
639    0x9430F05CFEF8ACBB, 0xA73038463A9AD3BD, 0xE5BA4E94FDA81C6C, 0x51AB3C69201906E1,
640    0x2613EFCF23567038, 0x3ED865C6161C8A89, 0x58DC09289EA03658, 0x376277BE6E4E62AA,
641    0x3C90B273B9870A06, 0x9FE0F5164AA8F837, 0xB9905EEE7D3AEB79, 0x4BA2F4CAA4F1EB01,
642    0x01C2973BD37D564B, 0x7D21243A206BD8A7, 0xB435428BA8DD3DB7, 0x045541BCCE000F5F,
643    0xCEA89305914BEB1B, 0xE84B59A4A18CC1AE, 0xB5CC96326ADC69F3, 0xB4957198C60BB6E7,
644    0xDB38C42E2947EFC3, 0x9D2BBFA07C18C320, 0xA22C7B9C6CBFB72E, 0x6909BDC131B2E15E,
645    0xABECA69DD1395554, 0xC852ED7EE6817A61, 0x52B39B42F6D7D56B, 0x781D1803B8307C79,
646    0x386FFC16B79E3092, 0x55E7D5933870D116, 0xDE3828C68348493D, 0x8E288C8A3FBF741F,
647    0x0936252D32CDEC49, 0xACFE91F2BA885044, 0xE0A9ADFEA526F536, 0x41F97B86668C5972,
648    0xF9D8560A97AFA428, 0x2DBCC4250B75A871, 0x276434FFA80959F0, 0x4D3400D81937617D,
649    0x799C3EDF3F134590, 0x8B306D8372A740E9, 0x6707761FCCA9B861, 0x402134AE9488387F,
650    0xF2DA86FE2BAA7E67, 0x5DFDED45499AF1B4, 0x0AE292B1DE6B7A7D, 0x4799C3B88177704D,
651];
652
653/// Compact generator for TM8192 (rate 1/2, k=4096).
654/// P is 4096x4096, circulant_size=512, 8 blocks x 64 u64/row.
655#[rustfmt::skip]
656static TM8192_G: [u64; 8 * 64] = [
657    0x616DB583006DB999, 0x54780CD6DFC99087, 0x72D8260D390B1D46, 0x2A8F62DE88092161,
658    0x94BE0531EE408AEA, 0xF27F50F3AD71865A, 0xC7910EEF8824A858, 0xCA7B13FC843DAFB1,
659    0xBA3E0B010860D090, 0x66A8632E2B273DAB, 0xDF90C26FCDD989C2, 0x831874EA7FBA23D9,
660    0x40A294111C1B0C1C, 0xF62F56A376B94CF6, 0x4FA594B987B19226, 0xE525704D7F2BC66E,
661    0x226C671C22A59AC0, 0x62490596EB1536C9, 0xF66AE799C2489FAD, 0x2C131E29ED64A25C,
662    0xB0ADC88D04C5EC8F, 0xECD7F78B3825E626, 0x858CFAA0DE77772C, 0xE8822C7AA39628A0,
663    0x123B1C426E2A9336, 0x6D067D26DE51362E, 0xA0BA916EBD122952, 0x1B1B044459B32578,
664    0x5F3F3E24199B2460, 0x151E4CAA9FD26A5D, 0xC46BE0D6DA907EFA, 0xF38F413642F702F5,
665    0x324AFD5D62F4CC25, 0x1FF5C0FD95DE0FAB, 0x061F0C92CA5BC97F, 0x976118AD84E0663A,
666    0x3BF1B4F07D1CCCC2, 0xDF9E09D506B073DE, 0xD87CC0653C944FC7, 0xD438223C0DF3EB67,
667    0xE62AE13F8D4000D6, 0x16E814045495F6E9, 0x69C473B059386F5D, 0xDBCC25F4002EB132,
668    0xD73A98414D85346F, 0x55DEBFF875F7CB9D, 0x2466A412D180E0A1, 0xADA18D281376A671,
669    0x8EB0FB6BB7B9AD2A, 0x2132010511077F6B, 0xD424B6F5B578C11D, 0x0076B781930F755E,
670    0xBB72C41ED1751947, 0x6C257C31C3159BF3, 0x1FADA2755F1B8A23, 0xB22D6A428AA290E2,
671    0x54CC73C7599AB67C, 0x6807C4286BECF842, 0x3F3216EF04E1B6DE, 0x61349DDB23E3A0EB,
672    0x0EF70C5BE1AD91D3, 0x1B0BB532C1098DC6, 0x19BF80F3853EEA35, 0x7091C05D95170A7E,
673    0x5E6381A718C0A817, 0xF8101ECDCDBF825E, 0x732E4356CEC42C22, 0x2DBC476BD704837C,
674    0x382B7FBF282B739E, 0xDC22B5EEA2909F0E, 0xB3ACB9E41FE2AC79, 0x1130A36A9CBFC1D9,
675    0xD4F8DE28FA77F37E, 0x4A6B5A82A58CE917, 0xCA74C8397E9DB8ED, 0xCB2BF65DB9195445,
676    0x7707FE876DFF812D, 0x4B99466DF479A001, 0x14F27E702249DB3E, 0x9311301E9CE98703,
677    0x74FEAD0013FD861D, 0x67D7CE69D3635ECC, 0x6266E862D08B6307, 0x7B45D3098306EA74,
678    0x159DAEA2263E5870, 0x5EA5ABE58B7FD418, 0x62B9EC1D0F1BD47C, 0xD6CB42739C24F7FE,
679    0x7ACFF6D64C8E8F94, 0xBEABE280CFDCFCFB, 0x26AC7330073C25E0, 0x313DCB75E6C5261F,
680    0x15D82AFA665F73A4, 0xB4DA4E5D1648EAB0, 0x51EDEB9857C13C2F, 0x019FCBBA4F9DF2E1,
681    0x9CEFF1147D792C14, 0xAA2E211C3B9B94B2, 0xC9F24F49B0B1ED6E, 0x200C88D743F5AC1E,
682    0xE283C3A0AC79B9F1, 0xF496BDE74A2AA591, 0xACF2F526FB24413A, 0x58B495F91905F596,
683    0xD8F1469BCA9CC504, 0x1C50F1FB479CF268, 0x0503AD85BA2C0C6D, 0x01D2D739F3129315,
684    0xE49A9F57236D9585, 0xCC0B8A9B4BFE9ADC, 0xD97BED9006C33976, 0xACC00468693D56FA,
685    0x1EE66371B0EA6C4E, 0x1E172C2C5D76806C, 0xB7376B8CDEAD96B1, 0x4A1EC2B656298B94,
686    0x25EA2F0671082D70, 0xAA23C267D1F215C5, 0x9239AEB40186DF0A, 0xB284625DC6BAF45E,
687    0xFBFBE26BED98BB3B, 0x697764A6F82C9403, 0x9CBF14CB538A7D87, 0x801ACBD3A444A858,
688    0xBB74F0A4707592EE, 0x6B7DC6D21B8F6B4A, 0x184B567C8AA4CD82, 0x5EBF7F1EDCE015A5,
689    0x25453670647D23C5, 0xE445A705953F3BF4, 0xA5AF02E7BC46C969, 0xC8141D8782F171C9,
690    0xCFF7EBB20945DE5D, 0x363AD36D3BD5A0BA, 0x081C079CDD04B6E5, 0x968187C8A665344A,
691    0x23E9B1897A6FDF42, 0x7B5E910AA8D71F9C, 0xC6351474BC4563C2, 0x0FD38953295D3BA1,
692    0x5E7D1010503B7BA1, 0xC148251DB8A88AC6, 0x4E6AF8C1CC056E4E, 0xEF1C927FEC40C35D,
693    0x57140969483D9E33, 0x429FAFD177D031A4, 0x3B727CF832C8DFFE, 0x8D8960CB55BE4BE2,
694    0x7B69CC26F2FB731B, 0x53250D6F8EE7DFDA, 0x98812B9AAE9C02AE, 0x2FEDEA598D6B6E2F,
695    0x22B6CCA50541BD9F, 0x5D48565E551B310E, 0x10A0DFCB8035A5EC, 0x86EB9CD8C811CDCB,
696    0xCCCEC3732EF93EE8, 0xC9418E25CA5744E0, 0x7C45F9B161E277BC, 0xECE388B9B84AAEC4,
697    0xDA37FE277C72CB5C, 0xB1BE92AD37386740, 0x3E46B3535159687A, 0xDC79C39DEF7005C1,
698    0xF11F1CBD5F8877DA, 0x66AAC156EF27BB89, 0x3F5F1132336D52E8, 0xAEB60EACF9BEB3CF,
699    0xD204D92DFA496DAF, 0x564272E3FEC51CE5, 0x3C8F2DF6ACB191E6, 0x0E14CDEA28FD5ED0,
700    0xEBE09672ED11A3F6, 0x466FE3A967A4EC83, 0x90303059AE00DD83, 0x102A9F33B2943E4E,
701    0x6E56928E7FEE3333, 0xA36FF3EE7598744C, 0xF7C298FEF3EACC7C, 0xCC0F36DCBA6D87BD,
702    0xD441081163A65E27, 0xC958AF79C33A98B8, 0x1814015E77F82EF5, 0x120FBDAB540893B4,
703    0x7BEB68CC37F23835, 0xC91F5D36D6BA6F0A, 0x5E68FEBB6E6A2F24, 0x7EB5CF57684D0770,
704    0x249460788DFDC4A1, 0x218652BF881B4BB0, 0x6308EF86484E7070, 0xAACC72D3977CF5D0,
705    0x6230DEF1ACD4425F, 0x7B155A2A285CB2A3, 0x2CB9D46DA09B2816, 0x7826E77AEBD85F0C,
706    0x416595E136184841, 0x451F5B3E1F17D02C, 0x3DB32C2AF50091D6, 0x376406D8CB78A9E3,
707    0xD3B19911ACC45067, 0x9EAE25B0F290FF37, 0x2300F1A4BC91A43C, 0xB79DB270133D41DC,
708    0x4970F1420E71C0F8, 0x16EF938C3C17F0FC, 0xBB6E920ED853EAF6, 0xD2DC6792BF87098A,
709    0xB94C2E5DDE78C974, 0xAD6F423CD5ACA01E, 0xC9420AAF3FE83BEC, 0x31D47AACD3D62FA2,
710    0x476C38595BD66639, 0x368181E75B44BAA7, 0xADBC2B42E1D82D7A, 0x59312BB9A16F7D35,
711    0x0B13B44D828071E6, 0x9DD90DCD9B713A05, 0xFD8C21AA5E6E6D8D, 0xA49A5C3B34F98A4E,
712    0x5E822513F0DA2002, 0x35C65BFCA1DC2CE4, 0xAB21D146B778F680, 0x6680B8AC75285760,
713    0xFEF66B861AA67C76, 0x8A76D585DFADC8EB, 0x6556AD841DEA9F44, 0xACB42B6016142B6B,
714    0x69F1833474FADEB0, 0x400CE4D9F3BD62AD, 0x96E57F3E93DD2291, 0x80F2D4B5E77D098F,
715    0xEEBE2DFA4D4D86EC, 0xB07EEE9565FB5898, 0x55E1F53BA1B9784A, 0x8D195A0E37215512,
716    0x70089C535216636F, 0xBEB4D9E50A9EAC3D, 0xCB27891A7005A2AD, 0x87427E6B8326F6B3,
717    0xCA225C7B2A9EABFF, 0xDDDBC130B5342917, 0x848B029917BA98FF, 0xD6EF2389006A6B41,
718    0x7F678C61458EF625, 0xC96C0D3D07945ABB, 0x9836CF80823EB624, 0x4D86D114CC5DC2B1,
719    0x94F5D55C398B16A7, 0x1497C4CF102C2F10, 0x35C19D5DFC8A301B, 0x8DE33D41D909C15A,
720    0x3093B09E7489CE6A, 0xA14B331B70E76637, 0xFE6DDFFFA6DC4C51, 0x0371CB0D2A6EA3DA,
721    0xAC5F866DD75CD4C2, 0xD5959AC37DE4E1E8, 0x70313A5B2902F234, 0xCD939FE39F31FEBF,
722    0x8B46DAC906E3EBA9, 0xC3A74DE46E7A9140, 0xD3716667BB1EC22A, 0x87D5F8D048BDC5BA,
723    0x57B6024327CDDFF3, 0x296BE6508C48045B, 0x71FA519156F8C125, 0xF4E3B7356576F32C,
724    0x63BC588908C4E8B3, 0xF9F2D12A9E8F35B6, 0xFCF296C17FD8E8D0, 0x76406FA11D16175F,
725    0xCC45AE82D672979E, 0x8A0A359B2328C79A, 0xE61F87EBE04DAC93, 0x4303054865973200,
726    0x0CE627417B3F8CFD, 0x4A992E7F2B680216, 0xAF773385B9337E17, 0x43D43FD965282CF5,
727    0xAE71B0CAFEB4DA3E, 0x0B95F1341667C519, 0xFB9F89D7CEC711E5, 0x7485F04A965CDC83,
728    0x2CBEC0BE1B2A3E23, 0xB5EAF4C5DAD8767E, 0x054B2225A60B88BE, 0x1DB6A35E0BAEB237,
729    0xA206BC721B252D52, 0xEA1F8E311203DFF0, 0xAE8D65BD19860557, 0x01A3C7FEB2DDEDD2,
730    0xD57C3BBA6A2BC56A, 0x9157677D7B48AD29, 0x07927176F6B22E8A, 0x92F6E9863C9E16D9,
731    0x11B6209E06EFE6AC, 0xBBBA2214EF5AEAB9, 0xD76645476B2C16B8, 0xD14E1AE3F3A85188,
732    0x835922B914D3F32F, 0xE05B7987A2516B3D, 0x3C8983AE176DFD04, 0x349A45359B422E1E,
733    0x01CC2266F2B68A43, 0x23F8931D7AA37B1C, 0xBD70DC2FEE915923, 0x27207AA612179515,
734    0x0A0DC918704A1A29, 0x3778FE75A99FDCE7, 0x7E820D0905EF7AC7, 0x2A682F2487A6E0FE,
735    0x03F42D94FDE1C13F, 0x958DF61112DB4A27, 0xA8A8EF35087FD089, 0x729F0864C2706CCB,
736    0x2B6CBD91A9A7B7B3, 0x1E08EA3570A6E1BE, 0xD495FC84FACD829F, 0x3234B1D1DC574B67,
737    0x900AA49643295914, 0x1795C615CBAEA980, 0x02440A0D447EF990, 0x435E452CC690203B,
738    0xDEBCBA3EEFC7A7CE, 0x71EB54B1728AEA9E, 0xDE70A7E6A1A8AE86, 0x168709A899738CCB,
739    0xC5B7A094AEBEA8EC, 0x95A414A8DE5D3DBE, 0x6745CB0D330B7843, 0x5AC2BB6666BB2D43,
740    0xA19EAD3B3D9536D0, 0xBB92DB949570981C, 0x22805E7DEA452FA6, 0x49C84EDC4324A7FB,
741    0xE6A9CAF4EE484007, 0x20B8F84CAC3A4248, 0x3B7E571846E2A5F7, 0x7A983EE311179CEC,
742    0x2D99878FF5AA06AC, 0xA0CBBA63B36985E0, 0x970761E7F837650B, 0xC46C9A2EB1AEFA95,
743    0xAC4D8AA5C970BB55, 0xFDF3408356C9EB26, 0x83B6FEE593736B66, 0xB49C055BD6503EEF,
744    0x3C7CADD15C9B86DC, 0xA626E1ABF4B971D0, 0x4C0A9A5AEF8305C3, 0xD0E4CC02C32FA91E,
745    0xD8949EF8FEADF7DA, 0x39D395B52D2779A0, 0xB305C4FD10C33A43, 0x4878967D9321B483,
746    0x5C035CA5802C37F6, 0xDC1E39AC30337253, 0x114176BBB2657631, 0x7C72E9548F179A5A,
747    0xA200FC35B6A0934D, 0x57543A60F6114B7B, 0x0D78D8DD8932538E, 0x545D806A1D9E4739,
748    0x0F092501F4A470CF, 0x7B1F9144D0A8F1B0, 0xC3D607930A75E5A1, 0x50233DCEEDB4C10B,
749    0x217C8EB38D4D2A0E, 0xF12557321D504ECA, 0x670B41E496441FDE, 0x341F0232101D4E3F,
750    0x4158FF6F4EAECC07, 0x3AA811DD450F528B, 0xC6095868B7BF9539, 0x26056BD409E5FE36,
751    0xB82831B150B80A73, 0x6D6CF7B16660ADCD, 0x5E1F4DB96E36E33D, 0xCC2F1506C7B8B0F2,
752    0xA4EC362FB0CF7B8B, 0x3B08D6CD1AF74407, 0x29D4C3C02627AD87, 0x33A0C94B2EBAF526,
753    0xFDB4463E6F8FBAF5, 0x65B1C3320F5704A8, 0x7309E529842378EC, 0xB733784F1CBD85F4,
754    0xF87FB0525C7C4D30, 0x7061F74DE2FB3BDF, 0xBC77E04EAB75A64F, 0xFE51203AB925E807,
755    0x1D1101A16A2C41DB, 0xDCA94C128560BEFD, 0xA4ECA6F22B44C6E5, 0x085A23F84106E4FD,
756    0x870FAA789E03FC37, 0x086E67B69FC8EB64, 0x21AA57FBA27866DF, 0xF712D5FEDA21FC51,
757    0x76EE3CB2C4A8629C, 0x20FC646A7ADF2A4B, 0xE73DCEF53FC92606, 0x7EB9964996BCEE40,
758    0x3C5642CD2F8084E0, 0xC14D3627FAD9F018, 0x0DADF07331246C00, 0x7F3AF95CC9B451CC,
759    0x3638887EB493F5EE, 0x3361F07E00F115BC, 0x04AF404BE6BA3467, 0x322B37A8E6ABF477,
760    0x10D56C3BC751892C, 0xFD12F29CC4319D05, 0x62005562D05261D3, 0x9FDF528A11E65BBE,
761    0xA0BF07C52E9A9ED7, 0xAC3F0FB9196A450E, 0x162009509F20BEE7, 0x4FCC6316BC4824D9,
762    0x3CBAC25E470A7468, 0xA629EB520E980DE3, 0x1F8C8873F4ED21B5, 0x7AAEBF43A5754359,
763    0xCD089ABE54897567, 0x8C2123223CF3F345, 0xAE0CECF0A3726BFB, 0xB130E34169A874B6,
764    0xC4CDEFC0A05D7DA1, 0xEE475E5407F15353, 0x99086700874C1300, 0x0E2EE21DF3EEFB65,
765    0x4BEF6F2B4137DC6E, 0xF197D514E904B8F3, 0x1BAD6C846D6BD7D7, 0x480F4818C3C57B4C,
766    0x7F53F168E4802027, 0x3702071EE48EC534, 0x22C71C90AA026298, 0x2B82BB6FF3100D8A,
767    0xEB3E8F033DA73FA8, 0x2B3B93E50C60E593, 0x6A07D3218946588D, 0x0EFB39E1A55C0FB9,
768    0xDBA87DA50C4697EE, 0x2ED72B004301019E, 0x595B92A2F55F7F1B, 0x37C2030B79057F52,
769    0x59CA13359E16B10A, 0x7F8778BBAF5D45E3, 0x2C643B524022FE77, 0x7A8F557C14141D63,
770    0x8E84BC4DBB1CE586, 0x6CD0B89C1CC5C6F7, 0xBF7E25D2B4FC28A1, 0x6E67CF8BFAC4F4BD,
771    0xA612F30067700487, 0xB6584B1AD578659F, 0xC2B7443228B2B7B4, 0x43882DABBF55739C,
772    0xB9660F530631A2CF, 0xDCBE94D21692CAC0, 0x1DA9EB5048FFF17B, 0xC4FB5957E8C9DF1F,
773    0x29E0573D85359FB7, 0x924AABBDDDCD26F5, 0x740FFA6824FCFCBD, 0x53BF1DFB587E0667,
774    0x641DD3F82962F5E6, 0xEA26461279B0F694, 0x79645462983DBBBC, 0xC544DA90255121EA,
775    0xA97C7B71923F0382, 0xDF60C9E34D84CAC2, 0x89B578899EBCF924, 0xF4304B80581C9887,
776    0xB1198F074143DCC4, 0x324D7DF301466AC9, 0x7903E688DD2E9186, 0xEDD2D90C34202AA3,
777    0x90815D489B715FF6, 0x04788F335322DF5C, 0x8856FD85F753785A, 0x96F4B2561990F458,
778    0xC69D3F99A8ED1BE9, 0x9C3F5A14B19B37AC, 0x729B3F35ABF52006, 0xE814B597145FA3FD,
779    0x86A5A2038BB67CF8, 0x225BCCF7A587E0D0, 0x9B47D26BC4DB017F, 0x6A77B6DEC5AF5B11,
780    0x7E399D8A336358D4, 0xAABE9C8E7EAAF644, 0x7638F2DC66EF65C1, 0x00D06EE202013042,
781    0xAD845A43D23E66FB, 0xA72D9D56457D66C7, 0xE44D98ED1E5F1D06, 0x3A5D01043930E9C2,
782    0xEDED8BA9DEE5F9DF, 0xF91CD887F097B9A2, 0xDF0099E278C253E0, 0xA549C7A2D81078C6,
783    0x680566EA7A1E724A, 0x99B5D7099AED278A, 0x3065BBC64BED4411, 0x54DCD346D38C9771,
784    0x648D55656B16CF01, 0x2D0C6EC8F616D3B7, 0x58089A8147D731AE, 0x077D557204256F93,
785];
786// ── Code Definitions ─────────────────────────────────────────────
787
788/// TM2048: Rate 1/2, k=1024, n=2048.
789pub static CODE_TM2048: LdpcCode = LdpcCode {
790    n: 2048, k: 1024, punctured: 512,
791    circulant_size: 128, submatrix_size: 512,
792    mb: 3, nb: 5,
793    generator: &TM2048_G,
794    h_matrix: &TM_R12_H,
795    theta: &THETA_K,
796    phi: &PHI_M512,
797};
798
799/// TM1536: Rate 2/3, k=1024, n=1536.
800pub static CODE_TM1536: LdpcCode = LdpcCode {
801    n: 1536, k: 1024, punctured: 256,
802    circulant_size: 64, submatrix_size: 256,
803    mb: 3, nb: 7,
804    generator: &TM1536_G,
805    h_matrix: &TM_R23_H,
806    theta: &THETA_K,
807    phi: &PHI_M256,
808};
809
810/// TM1280: Rate 4/5, k=1024, n=1280.
811pub static CODE_TM1280: LdpcCode = LdpcCode {
812    n: 1280, k: 1024, punctured: 128,
813    circulant_size: 32, submatrix_size: 128,
814    mb: 3, nb: 11,
815    generator: &TM1280_G,
816    h_matrix: &TM_R45_H,
817    theta: &THETA_K,
818    phi: &PHI_M128,
819};
820
821/// TM8192: Rate 1/2, k=4096, n=8192.
822pub static CODE_TM8192: LdpcCode = LdpcCode {
823    n: 8192, k: 4096, punctured: 2048,
824    circulant_size: 512, submatrix_size: 2048,
825    mb: 3, nb: 5,
826    generator: &TM8192_G,
827    h_matrix: &TM_R12_H,
828    theta: &THETA_K,
829    phi: &PHI_M2048,
830};
831
832/// TM6144: Rate 2/3, k=4096, n=6144.
833pub static CODE_TM6144: LdpcCode = LdpcCode {
834    n: 6144, k: 4096, punctured: 1024,
835    circulant_size: 256, submatrix_size: 1024,
836    mb: 3, nb: 7,
837    generator: &TM6144_G,
838    h_matrix: &TM_R23_H,
839    theta: &THETA_K,
840    phi: &PHI_M1024,
841};
842
843/// TM5120: Rate 4/5, k=4096, n=5120.
844pub static CODE_TM5120: LdpcCode = LdpcCode {
845    n: 5120, k: 4096, punctured: 512,
846    circulant_size: 128, submatrix_size: 512,
847    mb: 3, nb: 11,
848    generator: &TM5120_G,
849    h_matrix: &TM_R45_H,
850    theta: &THETA_K,
851    phi: &PHI_M512,
852};
853
854// ── Encoding ─────────────────────────────────────────────────────
855
856/// Encodes information bits into a systematic LDPC codeword.
857///
858/// `info` has k/8 bytes of data. `output` receives n/8 bytes
859/// (info followed by parity).
860pub fn encode(
861    code: &LdpcCode,
862    info: &[u8],
863    output: &mut [u8],
864) -> Result<(), LdpcError> {
865    let k_bytes = code.info_bytes();
866    let n_bytes = code.codeword_bytes();
867
868    if info.len() < k_bytes {
869        return Err(LdpcError::WrongSize {
870            expected: k_bytes,
871            provided: info.len(),
872        });
873    }
874    if output.len() < n_bytes {
875        return Err(LdpcError::WrongSize {
876            expected: n_bytes,
877            provided: output.len(),
878        });
879    }
880
881    // Copy info bits to output, zero parity area.
882    output[..k_bytes].copy_from_slice(&info[..k_bytes]);
883    output[k_bytes..n_bytes].fill(0);
884
885    let parity = &mut output[k_bytes..n_bytes];
886    let b = code.circulant_size;
887    let k = code.k;
888    let r = code.parity_bits();
889    let gc = code.generator;
890    let row_len = r / 64;
891
892    // Encode using compact generator with circulant rotation.
893    for offset in 0..b {
894        for crow in 0..k / b {
895            let bit = crow * b + offset;
896            let byte = bit / 8;
897            let mask = 0x80u8 >> (bit % 8);
898            if info[byte] & mask != 0 {
899                // XOR this generator row into parity.
900                let row_start = crow * row_len;
901                for (idx, &circ) in
902                    gc[row_start..row_start + row_len].iter().enumerate()
903                {
904                    parity[idx * 8 + 7] ^= (circ >> 0) as u8;
905                    parity[idx * 8 + 6] ^= (circ >> 8) as u8;
906                    parity[idx * 8 + 5] ^= (circ >> 16) as u8;
907                    parity[idx * 8 + 4] ^= (circ >> 24) as u8;
908                    parity[idx * 8 + 3] ^= (circ >> 32) as u8;
909                    parity[idx * 8 + 2] ^= (circ >> 40) as u8;
910                    parity[idx * 8 + 1] ^= (circ >> 48) as u8;
911                    parity[idx * 8 + 0] ^= (circ >> 56) as u8;
912                }
913            }
914        }
915        // Left-rotate each parity block to simulate generator rotation.
916        for block in 0..r / b {
917            let start = block * b / 8;
918            let end = (block + 1) * b / 8;
919            let pblock = &mut parity[start..end];
920            let mut carry = pblock[0] >> 7;
921            for x in pblock.iter_mut().rev() {
922                let c = *x >> 7;
923                *x = (*x << 1) | carry;
924                carry = c;
925            }
926        }
927    }
928
929    Ok(())
930}
931
932// ── Syndrome Check ───────────────────────────────────────────────
933
934/// Maximum submatrix size in bytes for stack-allocated punctured
935/// bit buffer (M=2048 → 256 bytes).
936const MAX_PUNCT_BYTES: usize = 256;
937
938/// Checks if hard decisions satisfy all parity checks.
939///
940/// `codeword` is n bits packed in n/8 bytes. Returns true if valid.
941///
942/// Internally recovers the punctured bits (last H-matrix column)
943/// from row 2 of the parity check matrix (which has an identity
944/// block for the punctured column), then verifies rows 0 and 1.
945pub fn syndrome_check(code: &LdpcCode, codeword: &[u8]) -> bool {
946    let m = code.submatrix_size;
947    let punct_bytes = m / 8;
948    assert!(punct_bytes <= MAX_PUNCT_BYTES);
949
950    // Step 1: Recover punctured bits from row 2 checks.
951    // For row 2, the punctured column (nb-1) contributes via
952    // identity (HI) in layer 0 only. So:
953    //   punctured[pos] = XOR of all other row-2 contributions.
954    let mut punct = [0u8; MAX_PUNCT_BYTES];
955    for pos in 0..m {
956        let mut par = 0u8;
957        for col in 0..code.nb - 1 {
958            for layer in 0..3 {
959                let entry = code.h_matrix[layer][2][col];
960                if entry == 0 {
961                    continue;
962                }
963                let mapped =
964                    block_map(entry, pos, m, code.theta, code.phi);
965                if let Some(var_pos) = mapped {
966                    let bit_idx = col * m + var_pos;
967                    let byte = bit_idx / 8;
968                    let mask = 0x80 >> (bit_idx % 8);
969                    if codeword[byte] & mask != 0 {
970                        par ^= 1;
971                    }
972                }
973            }
974        }
975        // punct[pos] = par (the value that makes row-2 check = 0).
976        if par != 0 {
977            let byte = pos / 8;
978            let mask = 0x80 >> (pos % 8);
979            punct[byte] |= mask;
980        }
981    }
982
983    // Step 2: Check rows 0 and 1 using transmitted + punctured bits.
984    for row in 0..2 {
985        for pos in 0..m {
986            let mut parity = 0u8;
987            for col in 0..code.nb {
988                for layer in 0..3 {
989                    let entry = code.h_matrix[layer][row][col];
990                    if entry == 0 {
991                        continue;
992                    }
993                    let mapped = block_map(
994                        entry, pos, m, code.theta, code.phi,
995                    );
996                    if let Some(var_pos) = mapped {
997                        if col == code.nb - 1 {
998                            // Punctured column: use recovered bits.
999                            let byte = var_pos / 8;
1000                            let mask = 0x80 >> (var_pos % 8);
1001                            if punct[byte] & mask != 0 {
1002                                parity ^= 1;
1003                            }
1004                        } else {
1005                            let bit_idx = col * m + var_pos;
1006                            let byte = bit_idx / 8;
1007                            let mask = 0x80 >> (bit_idx % 8);
1008                            if codeword[byte] & mask != 0 {
1009                                parity ^= 1;
1010                            }
1011                        }
1012                    }
1013                }
1014            }
1015            if parity != 0 {
1016                return false;
1017            }
1018        }
1019    }
1020    true
1021}
1022
1023/// Maps a block entry + position to the variable node position.
1024///
1025/// Returns None for zero blocks, Some(mapped_pos) for identity
1026/// or PI_K blocks.
1027fn block_map(
1028    entry: u8,
1029    pos: usize,
1030    m: usize,
1031    theta: &[u8; 26],
1032    phi: &[[u16; 26]; 4],
1033) -> Option<usize> {
1034    let block_type = entry & TYPE_MASK;
1035    match block_type {
1036        _ if block_type == HZ || entry == 0 => None,
1037        _ if block_type == HI => Some(pos),
1038        _ if block_type == HP => {
1039            let k_idx = (entry & INDEX_MASK) as usize;
1040            Some(pi_k(pos, k_idx, m, theta, phi))
1041        }
1042        _ => None,
1043    }
1044}
1045
1046// ── Decoding (Layered Min-Sum Belief Propagation) ────────────────
1047
1048/// Maximum edges per check node across all AR4JA rates.
1049const MAX_EDGES_PER_CHECK: usize = 18;
1050
1051/// Maximum total non-zero H entries (3 rows × 18 max per row).
1052const MAX_ENTRIES: usize = 54;
1053
1054/// Maximum c2v storage (15 entries × M=2048 for TM8192).
1055const MAX_C2V: usize = 30720;
1056
1057/// Maximum variable nodes (n + punctured) for k≤4096 codes.
1058const MAX_TOTAL_VARS: usize = 10240;
1059
1060/// Checks if hard decisions from LLR signs satisfy all checks.
1061fn parity_ok(
1062    code: &LdpcCode,
1063    llr: &[i16],
1064    edges: &[(u8, u8)],
1065    row_start: &[usize; 3],
1066    row_count: &[usize; 3],
1067    m: usize,
1068) -> bool {
1069    let n = code.n;
1070    for row in 0..code.mb {
1071        let rs = row_start[row];
1072        let rc = row_count[row];
1073        for pos in 0..m {
1074            let mut parity = 0u8;
1075            for j in 0..rc {
1076                let (entry, col) = edges[rs + j];
1077                let mapped = block_map(
1078                    entry, pos, m, code.theta, code.phi,
1079                )
1080                .unwrap();
1081                let vi = if (col as usize) == code.nb - 1 {
1082                    n + mapped
1083                } else {
1084                    (col as usize) * m + mapped
1085                };
1086                if llr[vi] < 0 {
1087                    parity ^= 1;
1088                }
1089            }
1090            if parity != 0 {
1091                return false;
1092            }
1093        }
1094    }
1095    true
1096}
1097
1098/// Decodes using layered min-sum belief propagation.
1099///
1100/// `llr` must have at least `code.n + code.punctured` entries.
1101/// The first `code.n` are channel LLRs (positive = likely 0,
1102/// negative = likely 1). The remaining `code.punctured` entries
1103/// should be 0 (no channel information for punctured bits).
1104///
1105/// Returns `Ok(iterations)` on success (0 = already valid).
1106/// After return, hard decision for bit `i`: 0 if `llr[i] >= 0`,
1107/// 1 if `llr[i] < 0`. The first `code.k` bits are decoded info.
1108///
1109/// Currently supports k=1024 codes only.
1110pub fn decode(
1111    code: &LdpcCode,
1112    llr: &mut [i16],
1113    max_iters: usize,
1114) -> Result<usize, LdpcError> {
1115    let m = code.submatrix_size;
1116    let n = code.n;
1117    let total_vars = n + code.punctured;
1118
1119    if llr.len() < total_vars {
1120        return Err(LdpcError::WrongSize {
1121            expected: total_vars,
1122            provided: llr.len(),
1123        });
1124    }
1125
1126    // Enumerate non-zero H matrix entries, grouped by row.
1127    let mut edges = [(0u8, 0u8); MAX_ENTRIES];
1128    let mut row_start = [0usize; 3];
1129    let mut row_count = [0usize; 3];
1130    let mut total_entries = 0usize;
1131
1132    for row in 0..code.mb {
1133        row_start[row] = total_entries;
1134        for layer in 0..3 {
1135            for col in 0..code.nb {
1136                let e = code.h_matrix[layer][row][col];
1137                if e == 0 {
1138                    continue;
1139                }
1140                let btype = e & TYPE_MASK;
1141                if btype == HI || btype == HP {
1142                    edges[total_entries] = (e, col as u8);
1143                    total_entries += 1;
1144                }
1145            }
1146        }
1147        row_count[row] = total_entries - row_start[row];
1148    }
1149
1150    assert!(total_entries * m <= MAX_C2V);
1151
1152    // Check-to-variable messages, initialized to zero.
1153    let mut c2v = [0i16; MAX_C2V];
1154
1155    for iter in 0..max_iters {
1156        // Early termination: all parity checks satisfied?
1157        if parity_ok(
1158            code,
1159            llr,
1160            &edges[..total_entries],
1161            &row_start,
1162            &row_count,
1163            m,
1164        ) {
1165            return Ok(iter);
1166        }
1167
1168        // Layered min-sum: process each block-row of H.
1169        for row in 0..code.mb {
1170            let rs = row_start[row];
1171            let rc = row_count[row];
1172
1173            for pos in 0..m {
1174                // Step 1: v2c = llr[var] - old c2v.
1175                let mut v2c = [0i16; MAX_EDGES_PER_CHECK];
1176                let mut vi = [0usize; MAX_EDGES_PER_CHECK];
1177
1178                for j in 0..rc {
1179                    let (entry, col) = edges[rs + j];
1180                    let mapped = block_map(
1181                        entry, pos, m, code.theta, code.phi,
1182                    )
1183                    .unwrap();
1184                    vi[j] = if (col as usize) == code.nb - 1 {
1185                        n + mapped
1186                    } else {
1187                        (col as usize) * m + mapped
1188                    };
1189                    let ci = (rs + j) * m + pos;
1190                    v2c[j] =
1191                        llr[vi[j]].saturating_sub(c2v[ci]);
1192                }
1193
1194                // Step 2: two smallest magnitudes + sign parity.
1195                let mut sign_bits = 0u32;
1196                let mut min1: u16 = u16::MAX;
1197                let mut min2: u16 = u16::MAX;
1198                let mut min1_j: usize = 0;
1199
1200                for j in 0..rc {
1201                    if v2c[j] < 0 {
1202                        sign_bits |= 1 << j;
1203                    }
1204                    let mag = v2c[j].unsigned_abs();
1205                    if mag < min1 {
1206                        min2 = min1;
1207                        min1 = mag;
1208                        min1_j = j;
1209                    } else if mag < min2 {
1210                        min2 = mag;
1211                    }
1212                }
1213
1214                let total_sign =
1215                    (sign_bits.count_ones() & 1) as u8;
1216
1217                // Step 3: new c2v and LLR update.
1218                for j in 0..rc {
1219                    let mag =
1220                        if j == min1_j { min2 } else { min1 };
1221                    // Normalized min-sum: scale by 3/4.
1222                    let scaled =
1223                        ((mag as u32 * 3) >> 2) as i16;
1224                    let j_sign =
1225                        ((sign_bits >> j) & 1) as u8;
1226                    let neg = total_sign ^ j_sign;
1227                    let new_c2v = if neg != 0 {
1228                        -scaled
1229                    } else {
1230                        scaled
1231                    };
1232
1233                    let ci = (rs + j) * m + pos;
1234                    let old = c2v[ci];
1235                    c2v[ci] = new_c2v;
1236                    llr[vi[j]] = llr[vi[j]]
1237                        .saturating_sub(old)
1238                        .saturating_add(new_c2v);
1239                }
1240            }
1241        }
1242    }
1243
1244    // Final check after last iteration.
1245    if parity_ok(
1246        code,
1247        llr,
1248        &edges[..total_entries],
1249        &row_start,
1250        &row_count,
1251        m,
1252    ) {
1253        Ok(max_iters)
1254    } else {
1255        Err(LdpcError::DecodingFailed)
1256    }
1257}
1258
1259/// Convenience decoder for hard-decision received data.
1260///
1261/// `received` is `code.n / 8` bytes. `decoded` receives
1262/// `code.k / 8` bytes of corrected information.
1263/// Returns `Ok(iterations)` on success.
1264pub fn decode_hard(
1265    code: &LdpcCode,
1266    received: &[u8],
1267    decoded: &mut [u8],
1268    max_iters: usize,
1269) -> Result<usize, LdpcError> {
1270    let n_bytes = code.codeword_bytes();
1271    if received.len() < n_bytes {
1272        return Err(LdpcError::WrongSize {
1273            expected: n_bytes,
1274            provided: received.len(),
1275        });
1276    }
1277    let k_bytes = code.info_bytes();
1278    if decoded.len() < k_bytes {
1279        return Err(LdpcError::WrongSize {
1280            expected: k_bytes,
1281            provided: decoded.len(),
1282        });
1283    }
1284
1285    let total = code.n + code.punctured;
1286    assert!(total <= MAX_TOTAL_VARS);
1287    let mut llr = [0i16; MAX_TOTAL_VARS];
1288    bytes_to_llr(received, &mut llr, code.n);
1289    // Punctured entries stay at 0 (no channel info).
1290
1291    let iters = decode(code, &mut llr[..total], max_iters)?;
1292    llr_to_bytes(&llr, decoded, code.k);
1293    Ok(iters)
1294}
1295
1296// ── Helpers ──────────────────────────────────────────────────────
1297
1298/// Converts packed bytes to hard LLR values.
1299///
1300/// Each bit becomes +127 (for 0) or -127 (for 1).
1301pub fn bytes_to_llr(bytes: &[u8], llr: &mut [i16], n_bits: usize) {
1302    for bit in 0..n_bits {
1303        let byte = bit / 8;
1304        let mask = 0x80 >> (bit % 8);
1305        llr[bit] = if bytes[byte] & mask != 0 { -127 } else { 127 };
1306    }
1307}
1308
1309/// Extracts hard decisions from LLR values into packed bytes.
1310///
1311/// Bit `i` is 0 if `llr[i] >= 0`, 1 if `llr[i] < 0`.
1312pub fn llr_to_bytes(llr: &[i16], bytes: &mut [u8], n_bits: usize) {
1313    let n_bytes = (n_bits + 7) / 8;
1314    bytes[..n_bytes].fill(0);
1315    for bit in 0..n_bits {
1316        if llr[bit] < 0 {
1317            bytes[bit / 8] |= 0x80 >> (bit % 8);
1318        }
1319    }
1320}
1321
1322/// LDPC encoder implementing [`FecEncoder`](crate::coding::FecEncoder).
1323pub struct LdpcFecEncoder {
1324    code: &'static LdpcCode,
1325}
1326
1327impl LdpcFecEncoder {
1328    /// Creates an encoder for the given LDPC code.
1329    pub fn new(code: &'static LdpcCode) -> Self {
1330        Self { code }
1331    }
1332}
1333
1334impl crate::coding::FecEncoder for LdpcFecEncoder {
1335    type Error = LdpcError;
1336
1337    fn encode(&self, data: &[u8], output: &mut [u8]) -> Result<usize, Self::Error> {
1338        encode(self.code, data, output)?;
1339        Ok(self.code.codeword_bytes())
1340    }
1341}
1342
1343/// LDPC hard-decision decoder implementing [`FecDecoder`](crate::coding::FecDecoder).
1344pub struct LdpcFecDecoder {
1345    code: &'static LdpcCode,
1346    max_iters: usize,
1347}
1348
1349impl LdpcFecDecoder {
1350    /// Creates a decoder for the given LDPC code and iteration limit.
1351    pub fn new(code: &'static LdpcCode, max_iters: usize) -> Self {
1352        Self { code, max_iters }
1353    }
1354}
1355
1356impl crate::coding::FecDecoder for LdpcFecDecoder {
1357    type Error = LdpcError;
1358
1359    fn decode(&self, data: &mut [u8]) -> Result<usize, Self::Error> {
1360        let mut decoded = [0u8; 512]; // k/8 max = 4096/8 = 512
1361        let iters = decode_hard(self.code, data, &mut decoded, self.max_iters)?;
1362        let k_bytes = self.code.info_bytes();
1363        data[..k_bytes].copy_from_slice(&decoded[..k_bytes]);
1364        Ok(iters)
1365    }
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370    use super::*;
1371
1372    #[test]
1373    fn encode_all_zeros_tm2048() {
1374        let code = &CODE_TM2048;
1375        let info = [0u8; 128];
1376        let mut codeword = [0u8; 256];
1377        encode(code, &info, &mut codeword).unwrap();
1378        // All-zeros is always a valid codeword.
1379        assert!(codeword.iter().all(|&b| b == 0));
1380    }
1381
1382    #[test]
1383    fn encode_all_zeros_tm1536() {
1384        let code = &CODE_TM1536;
1385        let info = [0u8; 128];
1386        let mut codeword = [0u8; 192];
1387        encode(code, &info, &mut codeword).unwrap();
1388        assert!(codeword.iter().all(|&b| b == 0));
1389    }
1390
1391    #[test]
1392    fn encode_all_zeros_tm1280() {
1393        let code = &CODE_TM1280;
1394        let info = [0u8; 128];
1395        let mut codeword = [0u8; 160];
1396        encode(code, &info, &mut codeword).unwrap();
1397        assert!(codeword.iter().all(|&b| b == 0));
1398    }
1399
1400    #[test]
1401    fn encode_systematic_tm2048() {
1402        let code = &CODE_TM2048;
1403        let mut info = [0u8; 128];
1404        info[0] = 0xA5;
1405        info[1] = 0x3C;
1406        info[127] = 0x01;
1407
1408        let mut codeword = [0u8; 256];
1409        encode(code, &info, &mut codeword).unwrap();
1410
1411        // Info bits at the front (systematic).
1412        assert_eq!(&codeword[..128], &info[..]);
1413        // Parity should be non-zero.
1414        assert!(codeword[128..].iter().any(|&b| b != 0));
1415    }
1416
1417    #[test]
1418    fn syndrome_check_valid_tm2048() {
1419        let code = &CODE_TM2048;
1420        let mut info = [0u8; 128];
1421        info[5] = 0x42;
1422
1423        let mut codeword = [0u8; 256];
1424        encode(code, &info, &mut codeword).unwrap();
1425
1426        assert!(syndrome_check(code, &codeword));
1427    }
1428
1429    #[test]
1430    fn syndrome_check_detects_error_tm2048() {
1431        let code = &CODE_TM2048;
1432        let mut info = [0u8; 128];
1433        info[5] = 0x42;
1434
1435        let mut codeword = [0u8; 256];
1436        encode(code, &info, &mut codeword).unwrap();
1437
1438        // Flip one bit.
1439        codeword[0] ^= 0x80;
1440        assert!(!syndrome_check(code, &codeword));
1441    }
1442
1443    #[test]
1444    fn syndrome_check_valid_tm1536() {
1445        let code = &CODE_TM1536;
1446        let mut info = [0u8; 128];
1447        info[10] = 0xAB;
1448        let mut codeword = [0u8; 192];
1449        encode(code, &info, &mut codeword).unwrap();
1450        assert!(syndrome_check(code, &codeword));
1451    }
1452
1453    #[test]
1454    fn syndrome_check_valid_tm1280() {
1455        let code = &CODE_TM1280;
1456        let mut info = [0u8; 128];
1457        info[10] = 0xAB;
1458        let mut codeword = [0u8; 160];
1459        encode(code, &info, &mut codeword).unwrap();
1460        assert!(syndrome_check(code, &codeword));
1461    }
1462
1463    #[test]
1464    fn pi_k_permutation_is_bijective() {
1465        // Verify PI_K is a valid permutation (bijective) for M=512.
1466        let m = 512;
1467        let mut seen = [false; 512];
1468        for i in 0..m {
1469            let j = pi_k(i, 0, m, &THETA_K, &PHI_M512);
1470            assert!(j < m, "pi_k({i}, 0) = {j} >= M={m}");
1471            assert!(!seen[j], "pi_k maps two inputs to {j}");
1472            seen[j] = true;
1473        }
1474        assert!(seen.iter().all(|&s| s));
1475    }
1476
1477    #[test]
1478    fn wrong_size_errors() {
1479        let code = &CODE_TM2048;
1480        let info = [0u8; 64]; // too short
1481        let mut output = [0u8; 256];
1482        assert!(matches!(
1483            encode(code, &info, &mut output),
1484            Err(LdpcError::WrongSize { .. })
1485        ));
1486    }
1487
1488    #[test]
1489    fn encode_matches_labrador_tm1280() {
1490        let code = &CODE_TM1280;
1491        let mut info = [0u8; 128];
1492        for i in 0..128 {
1493            info[i] = i as u8;
1494        }
1495        let mut codeword = [0u8; 160];
1496        encode(code, &info, &mut codeword).unwrap();
1497        let expected: [u8; 32] = [
1498            0xF1, 0x68, 0xE0, 0x79, 0x45, 0xE3, 0x08, 0xAE,
1499            0xEF, 0xD1, 0x68, 0x56, 0x60, 0x0A, 0x90, 0xFA,
1500            0xF6, 0x55, 0xA2, 0x01, 0x60, 0x77, 0xF7, 0xE0,
1501            0xFA, 0xB5, 0x49, 0x06, 0xDD, 0x6D, 0xCD, 0x7D,
1502        ];
1503        assert_eq!(&codeword[128..], &expected[..]);
1504    }
1505
1506    #[test]
1507    fn encode_matches_labrador_tm1536() {
1508        let code = &CODE_TM1536;
1509        let mut info = [0u8; 128];
1510        for i in 0..128 {
1511            info[i] = i as u8;
1512        }
1513        let mut codeword = [0u8; 192];
1514        encode(code, &info, &mut codeword).unwrap();
1515        let expected: [u8; 64] = [
1516            0x99, 0x4D, 0x02, 0x17, 0x53, 0x87, 0xC8, 0xDD,
1517            0x42, 0x2E, 0x46, 0x29, 0x06, 0x6A, 0x02, 0x6D,
1518            0xE1, 0xAB, 0xB9, 0xA2, 0xAA, 0xE0, 0xF2, 0xE9,
1519            0xF6, 0xAA, 0xE6, 0xF0, 0x42, 0x1E, 0x52, 0x44,
1520            0x5F, 0x62, 0xD1, 0xA8, 0x8F, 0xB2, 0x01, 0x78,
1521            0xB1, 0xD6, 0x2D, 0x0B, 0xD6, 0xB1, 0x4A, 0x6C,
1522            0x93, 0x26, 0x69, 0xAA, 0xE0, 0x55, 0x1A, 0xD9,
1523            0x9B, 0x94, 0x35, 0x27, 0x3F, 0x30, 0x91, 0x83,
1524        ];
1525        assert_eq!(&codeword[128..], &expected[..]);
1526    }
1527
1528    /// Verify encoder matches labrador-ldpc test vectors.
1529    #[test]
1530    fn encode_matches_labrador_tm2048() {
1531        let code = &CODE_TM2048;
1532        let mut info = [0u8; 128];
1533        for i in 0..128 {
1534            info[i] = i as u8;
1535        }
1536        let mut codeword = [0u8; 256];
1537        encode(code, &info, &mut codeword).unwrap();
1538
1539        let expected_parity: [u8; 128] = [
1540            0xEE, 0xA9, 0xAA, 0xAF, 0x98, 0xD9, 0x16, 0xCE,
1541            0x6C, 0x2B, 0x28, 0x2D, 0x1A, 0x5B, 0x94, 0x4C,
1542            0xA4, 0xF1, 0xB3, 0xD3, 0x1A, 0xEC, 0x58, 0x5A,
1543            0xB3, 0xE6, 0xA4, 0xC4, 0x0D, 0xFB, 0x4F, 0x4D,
1544            0xD8, 0x07, 0xA1, 0xAD, 0x0A, 0xE9, 0x62, 0xC4,
1545            0xD4, 0x0B, 0xAD, 0xA1, 0x06, 0xE5, 0x6E, 0xC8,
1546            0xA6, 0x68, 0x6A, 0xD5, 0xE6, 0xAC, 0x09, 0xBE,
1547            0x3F, 0xF1, 0xF3, 0x4C, 0x7F, 0x35, 0x90, 0x27,
1548            0xF2, 0x64, 0x69, 0x03, 0x83, 0x37, 0x42, 0x91,
1549            0x21, 0xB7, 0xBA, 0xD0, 0x50, 0xE4, 0x91, 0x42,
1550            0xE4, 0x0D, 0x64, 0x19, 0x70, 0x84, 0xA5, 0xB7,
1551            0x86, 0x6F, 0x06, 0x7B, 0x12, 0xE6, 0xC7, 0xD5,
1552            0xAB, 0x10, 0xDB, 0x03, 0x4F, 0xF6, 0x8A, 0xFE,
1553            0x17, 0xAC, 0x67, 0xBF, 0xF3, 0x4A, 0x36, 0x42,
1554            0x04, 0xAE, 0x85, 0xB3, 0xB6, 0x47, 0xCE, 0xC4,
1555            0x0F, 0xA5, 0x8E, 0xB8, 0xBD, 0x4C, 0xC5, 0xCF,
1556        ];
1557        assert_eq!(&codeword[128..], &expected_parity[..]);
1558    }
1559
1560    // ── Decoder tests ────────────────────────────────────────────
1561
1562    #[test]
1563    fn decode_valid_codeword() {
1564        // A valid codeword still needs ≥1 iteration because
1565        // punctured bits start at LLR=0 (unknown to receiver).
1566        let code = &CODE_TM2048;
1567        let mut info = [0u8; 128];
1568        info[5] = 0x42;
1569        let mut codeword = [0u8; 256];
1570        encode(code, &info, &mut codeword).unwrap();
1571
1572        let mut decoded = [0u8; 128];
1573        decode_hard(code, &codeword, &mut decoded, 50).unwrap();
1574        assert_eq!(&decoded[..], &info[..]);
1575    }
1576
1577    #[test]
1578    fn decode_corrects_1_error_tm2048() {
1579        let code = &CODE_TM2048;
1580        let mut info = [0u8; 128];
1581        info[5] = 0x42;
1582        let mut codeword = [0u8; 256];
1583        encode(code, &info, &mut codeword).unwrap();
1584
1585        codeword[0] ^= 0x80; // flip 1 bit
1586
1587        let mut decoded = [0u8; 128];
1588        let iters =
1589            decode_hard(code, &codeword, &mut decoded, 50)
1590                .unwrap();
1591        assert!(iters > 0);
1592        assert_eq!(&decoded[..], &info[..]);
1593    }
1594
1595    #[test]
1596    fn decode_corrects_errors_tm1536() {
1597        let code = &CODE_TM1536;
1598        let mut info = [0u8; 128];
1599        for i in 0..128 {
1600            info[i] = i as u8;
1601        }
1602        let mut codeword = [0u8; 192];
1603        encode(code, &info, &mut codeword).unwrap();
1604
1605        codeword[0] ^= 0x80;
1606        codeword[10] ^= 0x01;
1607
1608        let mut decoded = [0u8; 128];
1609        decode_hard(code, &codeword, &mut decoded, 50).unwrap();
1610        assert_eq!(&decoded[..], &info[..]);
1611    }
1612
1613    #[test]
1614    fn decode_corrects_errors_tm1280() {
1615        let code = &CODE_TM1280;
1616        let mut info = [0u8; 128];
1617        for i in 0..128 {
1618            info[i] = i as u8;
1619        }
1620        let mut codeword = [0u8; 160];
1621        encode(code, &info, &mut codeword).unwrap();
1622
1623        codeword[3] ^= 0x04;
1624
1625        let mut decoded = [0u8; 128];
1626        decode_hard(code, &codeword, &mut decoded, 50).unwrap();
1627        assert_eq!(&decoded[..], &info[..]);
1628    }
1629
1630    #[test]
1631    fn decode_hard_roundtrip_all_rates() {
1632        for code in
1633            [&CODE_TM2048, &CODE_TM1536, &CODE_TM1280]
1634        {
1635            let mut info = [0u8; 128];
1636            for i in 0..128 {
1637                info[i] = (i ^ 0xAA) as u8;
1638            }
1639            let n_bytes = code.codeword_bytes();
1640            let mut codeword = [0u8; 256];
1641            encode(code, &info, &mut codeword[..n_bytes])
1642                .unwrap();
1643
1644            // Flip 1 bit in info area.
1645            codeword[7] ^= 0x10;
1646
1647            let mut decoded = [0u8; 128];
1648            decode_hard(
1649                code,
1650                &codeword[..n_bytes],
1651                &mut decoded,
1652                50,
1653            )
1654            .unwrap();
1655            assert_eq!(
1656                &decoded[..],
1657                &info[..],
1658                "failed for n={}",
1659                code.n
1660            );
1661        }
1662    }
1663
1664    // ── k=4096 tests ─────────────────────────────────────────────
1665
1666    // Max codeword bytes for k=4096 codes.
1667    const K4096_CW: usize = 1024; // 8192/8
1668
1669    #[test]
1670    fn encode_all_zeros_k4096() {
1671        for code in
1672            [&CODE_TM8192, &CODE_TM6144, &CODE_TM5120]
1673        {
1674            let info = [0u8; 512];
1675            let nb = code.codeword_bytes();
1676            let mut cw = [0u8; K4096_CW];
1677            encode(code, &info, &mut cw[..nb]).unwrap();
1678            assert!(
1679                cw[..nb].iter().all(|&b| b == 0),
1680                "all-zeros failed for n={}",
1681                code.n
1682            );
1683        }
1684    }
1685
1686    #[test]
1687    fn encode_systematic_k4096() {
1688        for code in
1689            [&CODE_TM8192, &CODE_TM6144, &CODE_TM5120]
1690        {
1691            let mut info = [0u8; 512];
1692            info[0] = 0xA5;
1693            info[511] = 0x01;
1694            let nb = code.codeword_bytes();
1695            let mut cw = [0u8; K4096_CW];
1696            encode(code, &info, &mut cw[..nb]).unwrap();
1697            assert_eq!(&cw[..512], &info[..]);
1698            assert!(cw[512..nb].iter().any(|&b| b != 0));
1699        }
1700    }
1701
1702    #[test]
1703    fn syndrome_check_valid_k4096() {
1704        for code in
1705            [&CODE_TM8192, &CODE_TM6144, &CODE_TM5120]
1706        {
1707            let mut info = [0u8; 512];
1708            for i in 0..512 {
1709                info[i] = (i & 0xFF) as u8;
1710            }
1711            let nb = code.codeword_bytes();
1712            let mut cw = [0u8; K4096_CW];
1713            encode(code, &info, &mut cw[..nb]).unwrap();
1714            assert!(
1715                syndrome_check(code, &cw[..nb]),
1716                "syndrome failed for n={}",
1717                code.n
1718            );
1719        }
1720    }
1721
1722    #[test]
1723    fn decode_corrects_error_tm8192() {
1724        let code = &CODE_TM8192;
1725        let mut info = [0u8; 512];
1726        for i in 0..512 {
1727            info[i] = (i & 0xFF) as u8;
1728        }
1729        let mut cw = [0u8; K4096_CW];
1730        encode(code, &info, &mut cw).unwrap();
1731        cw[0] ^= 0x80;
1732
1733        let total = code.n + code.punctured;
1734        let mut llr = [0i16; 10240];
1735        bytes_to_llr(&cw, &mut llr, code.n);
1736        let iters = decode(code, &mut llr[..total], 50)
1737            .unwrap();
1738        assert!(iters > 0);
1739        let mut decoded = [0u8; 512];
1740        llr_to_bytes(&llr, &mut decoded, code.k);
1741        assert_eq!(&decoded[..], &info[..]);
1742    }
1743
1744    #[test]
1745    fn decode_corrects_error_tm6144() {
1746        let code = &CODE_TM6144;
1747        let mut info = [0u8; 512];
1748        for i in 0..512 {
1749            info[i] = (i & 0xFF) as u8;
1750        }
1751        let nb = code.codeword_bytes();
1752        let mut cw = [0u8; K4096_CW];
1753        encode(code, &info, &mut cw[..nb]).unwrap();
1754        cw[10] ^= 0x01;
1755
1756        let total = code.n + code.punctured;
1757        let mut llr = [0i16; 10240];
1758        bytes_to_llr(&cw[..nb], &mut llr, code.n);
1759        decode(code, &mut llr[..total], 50).unwrap();
1760        let mut decoded = [0u8; 512];
1761        llr_to_bytes(&llr, &mut decoded, code.k);
1762        assert_eq!(&decoded[..], &info[..]);
1763    }
1764
1765    #[test]
1766    fn decode_corrects_error_tm5120() {
1767        let code = &CODE_TM5120;
1768        let mut info = [0u8; 512];
1769        for i in 0..512 {
1770            info[i] = (i & 0xFF) as u8;
1771        }
1772        let nb = code.codeword_bytes();
1773        let mut cw = [0u8; K4096_CW];
1774        encode(code, &info, &mut cw[..nb]).unwrap();
1775        cw[3] ^= 0x04;
1776
1777        let total = code.n + code.punctured;
1778        let mut llr = [0i16; 10240];
1779        bytes_to_llr(&cw[..nb], &mut llr, code.n);
1780        decode(code, &mut llr[..total], 50).unwrap();
1781        let mut decoded = [0u8; 512];
1782        llr_to_bytes(&llr, &mut decoded, code.k);
1783        assert_eq!(&decoded[..], &info[..]);
1784    }
1785}