├── CMakeLists.txt ├── README.md ├── build ├── rightsignal.raw └── turbosignal.raw ├── ccsds-guess.c ├── ccsds-guess.h ├── ccsds-modem.c ├── ccsds-modem.h ├── ccsds-tc.c ├── ccsds-tc.h ├── ccsds-tool.c ├── correlator.c ├── correlator.h ├── defs.h ├── lfsr.c ├── lfsr.h └── test.c /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set( 4 | CCSDS_TC_SOURCES 5 | ccsds-tc.c) 6 | 7 | set( 8 | CCSDS_TC_HEADERS 9 | ccsds-tc.h) 10 | 11 | project(ccsds_tc VERSION 0.1) 12 | 13 | add_executable( 14 | ccsds-tc-test 15 | ${CCSDS_TC_SOURCES} 16 | ${CCSDS_TC_HEADERS} test.c) 17 | 18 | add_executable( 19 | ccsds-tc-test-fast 20 | ${CCSDS_TC_SOURCES} 21 | ${CCSDS_TC_HEADERS} test.c) 22 | 23 | target_link_libraries( 24 | ccsds-tc-test 25 | m) 26 | 27 | target_link_libraries( 28 | ccsds-tc-test-fast 29 | m) 30 | 31 | target_compile_options( 32 | ccsds-tc-test-fast PRIVATE 33 | -DCCSDS_TC_INT_ARITHMETICS -DCCSDS_TC_NO_MAXSTAR -O9) 34 | 35 | add_executable( 36 | ccsds-tool 37 | ${CCSDS_TC_SOURCES} 38 | ${CCSDS_TC_HEADERS} 39 | ccsds-guess.c 40 | ccsds-guess.h 41 | ccsds-modem.c 42 | ccsds-modem.h 43 | ccsds-tool.c 44 | correlator.c 45 | correlator.h 46 | defs.h 47 | lfsr.c 48 | lfsr.h) 49 | 50 | target_link_libraries( 51 | ccsds-tool 52 | m) 53 | 54 | target_compile_options( 55 | ccsds-tool PRIVATE 56 | -DCCSDS_TC_NO_MAXSTAR -O9) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ccsds-tc 2 | `ccsds-tc` is a small project that attempts to systematize the decoding of space packets as received by ground stations of the Amateur DSN. 3 | This is basically EB3FRN's fault for talking me into this :P 4 | 5 | The project includes a custom implementation of small BCJR-based decoder for CCSDS turbo codes (according to CCSDS 131.0-B-3), a descrambler 6 | and the logic to compute the CRC16 of decoded frames. 7 | 8 | ## Build 9 | This is the typical CMake project, just change to build, run `cmake` and `make`: 10 | 11 | ``` 12 | % cd build 13 | % cmake .. 14 | % make 15 | ``` 16 | 17 | ## Running the project 18 | The program you may be looking for is `ccsds-tool`. It supports 2 execution modes: 19 | * **Guess mode**, in which the tool will open an already demodulated baseband signal at the channel's symbol rate and attempt to blindly guess the channel parameters, and 20 | * **Decoding mode**, in which you pass the decoder parameters to tool in the command line, and it attempts to decode the input in real time. 21 | 22 | By default, the tool operates in decoding mode, reading 32-bit complex float samples from the standard input and writing decoded (and CRC-checked) frames 23 | to the standard output. Samples can be read from a file or a TCP socket as well: 24 | 25 | ``` 26 | % ./ccsds-tool -f samples-LLR.raw -o frames.bin 27 | CCSDS tool v0.1 for the Amateur DSN by EA1IYR 28 | (c) 2021 Gonzalo J. Carracedo - https://actinid.org 29 | Code rate: 1/6 30 | Block length: 8920 bits 31 | Turbocode iters: 1 32 | Channel: Q 33 | Sync SNR: 19.03 dB 34 | Input file: /tmp/samples-LLR.raw 35 | Output file: frames.bin 36 | Decode rate: 882.88 kbps 37 | ./ccsds-tool: 82510 bytes decoded 38 | ``` 39 | 40 | Guess mode operates from existing files only and it is activated by means of the command line option `-g`: 41 | 42 | ``` 43 | % ./ccsds-tool -g samples-LLR.raw 44 | Looking for syncwords for r = 1/2 45 | Looking for syncwords for r = 1/3 46 | Looking for syncwords for r = 1/4 47 | Looking for syncwords for r = 1/6 48 | Candidate CCSDS turbocode found 49 | Code rate: 1/6 50 | Channel Q: 94 frames 51 | Frame length: 8920 bit 52 | ``` 53 | 54 | Details on other tool options can be obtained by running `ccsds-tool --help` 55 | 56 | -------------------------------------------------------------------------------- /build/rightsignal.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BatchDrake/ccsds-tc/791a6638092445816147c99b3406b56f36827e86/build/rightsignal.raw -------------------------------------------------------------------------------- /build/turbosignal.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BatchDrake/ccsds-tc/791a6638092445816147c99b3406b56f36827e86/build/turbosignal.raw -------------------------------------------------------------------------------- /ccsds-guess.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #include "ccsds-guess.h" 21 | #include "correlator.h" 22 | 23 | #include 24 | #include 25 | 26 | #define CCSDS_TC_SYNC_RATE_1_2 "0000001101000111011101101100011100100111001010001001010110110000" 27 | #define CCSDS_TC_SYNC_RATE_1_3 "001001011101010111000000110011101000100110010000111101101100100101000110000110111111011110011100" 28 | #define CCSDS_TC_SYNC_RATE_1_4 "00000011010001110111011011000111001001110010100010010101101100001111110010111000100010010011100011011000110101110110101001001111" 29 | #define CCSDS_TC_SYNC_RATE_1_6 "001001011101010111000000110011101000100110010000111101101100100101000110000110111111011110011100110110100010101000111111001100010111011001101111000010010011011010111001111001000000100001100011" 30 | 31 | #define CCSDS_TC_SYNCWORD_COUNT 4 32 | 33 | static const struct ccsds_tc_syncword g_syncwords[] = { 34 | {CCSDS_TC_SYNC_RATE_1_2, 2}, 35 | {CCSDS_TC_SYNC_RATE_1_3, 3}, 36 | {CCSDS_TC_SYNC_RATE_1_4, 4}, 37 | {CCSDS_TC_SYNC_RATE_1_6, 6} 38 | }; 39 | 40 | void 41 | ccsds_tc_report_init(struct ccsds_tc_report *self, const struct ccsds_tc_syncword *tcs) 42 | { 43 | memset(self, 0, sizeof(struct ccsds_tc_report)); 44 | self->tc_params = tcs; 45 | } 46 | 47 | const struct ccsds_tc_syncword * 48 | ccsds_tc_syncword_lookup(unsigned int rate_inv) 49 | { 50 | unsigned int i; 51 | 52 | for (i = 0; i < 4; ++i) 53 | if (g_syncwords[i].rate_inv == rate_inv) 54 | return &g_syncwords[i]; 55 | 56 | return NULL; 57 | } 58 | 59 | static inline unsigned int 60 | ccsds_tc_report_get_block_length(const struct ccsds_tc_report *self, unsigned int L) 61 | { 62 | unsigned int payload; 63 | unsigned int len; 64 | unsigned int rateinv = self->tc_params->rate_inv; 65 | 66 | if (L % rateinv != 0) 67 | return 0; 68 | 69 | L /= rateinv; 70 | 71 | payload = L - 32 - 4; 72 | 73 | if (payload % (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3) == 0) { 74 | len = payload / (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3); 75 | if (len < 1 || len > 5) 76 | return 0; 77 | 78 | return len; 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | bool 85 | ccsds_tc_report_find_frames( 86 | struct ccsds_tc_report *self, 87 | const float complex *__restrict x, 88 | unsigned int L, 89 | float SNR, 90 | bool channel) 91 | { 92 | correlator_t correlator = correlator_INITIALIZER; 93 | unsigned int i; 94 | unsigned int curr, last; 95 | unsigned int step; 96 | unsigned int block_info_len; 97 | 98 | bool ok = false; 99 | 100 | step = strlen(self->tc_params->syncword); 101 | 102 | if (L < 2 * step) 103 | return true; 104 | 105 | if (!correlator_init_from_string(&correlator, self->tc_params->syncword)) { 106 | fprintf( 107 | stderr, 108 | "%s: failed to initialize correlator for 1/%d syncword\n", 109 | __FUNCTION__, 110 | self->tc_params->rate_inv); 111 | goto done; 112 | } 113 | 114 | last = L; 115 | 116 | for (i = 0; i < L - 2 * step; i += step) { 117 | if (correlator_find_complex(&correlator, x + i, 2 * step, SNR, channel)) { 118 | curr = correlator_get_pos(&correlator) + i; 119 | 120 | if ( 121 | last < curr 122 | && (block_info_len = ccsds_tc_report_get_block_length(self, curr - last)) > 0) { 123 | printf( 124 | "%c channel: Potential 1/%d CCSDS frame @ pos = %7d (%7d sice last) (SNR: %.1f dB)\r", 125 | channel ? 'Q' : 'I', 126 | self->tc_params->rate_inv, 127 | correlator_get_pos(&correlator) + i, 128 | curr - last, 129 | 10 * log10(correlator_get_snr(&correlator))); 130 | 131 | ++self->histogram[block_info_len]; 132 | 133 | if (channel) 134 | ++self->count_q; 135 | else 136 | ++self->count_i; 137 | 138 | fflush(stdout); 139 | } 140 | 141 | last = curr; 142 | } 143 | } 144 | 145 | if ((self->count_i + self->count_q) > 0) 146 | printf("\033[2K"); 147 | 148 | ok = true; 149 | 150 | done: 151 | correlator_finalize(&correlator); 152 | 153 | return ok; 154 | } 155 | 156 | bool 157 | ccsds_tc_report_find_best( 158 | struct ccsds_tc_report *self, 159 | const float complex *__restrict x, 160 | unsigned int L, 161 | float SNR) 162 | { 163 | struct ccsds_tc_report report, best_report; 164 | unsigned int i; 165 | bool ok = false; 166 | 167 | memset(&best_report, 0, sizeof(struct ccsds_tc_report)); 168 | 169 | for (i = 0; i < CCSDS_TC_SYNCWORD_COUNT; ++i) { 170 | printf("Looking for syncwords for r = 1/%d\n", g_syncwords[i].rate_inv); 171 | ccsds_tc_report_init(&report, &g_syncwords[i]); 172 | 173 | ccsds_tc_report_find_frames(&report, x, L, SNR, false); 174 | ccsds_tc_report_find_frames(&report, x, L, SNR, true); 175 | 176 | if (ccsds_tc_report_count_frames(&report) 177 | > ccsds_tc_report_count_frames(&best_report)) 178 | best_report = report; 179 | } 180 | 181 | if (ccsds_tc_report_count_frames(&best_report) > 0) { 182 | printf("Candidate CCSDS turbocode found\n"); 183 | printf(" Code rate: 1/%d\n", best_report.tc_params->rate_inv); 184 | if (best_report.count_i > best_report.count_q) 185 | printf(" Channel I: %d frames\n", best_report.count_i); 186 | else 187 | printf(" Channel Q: %d frames\n", best_report.count_q); 188 | 189 | printf( 190 | " Frame length: %d bits\n", 191 | ccsds_tc_report_get_candidate_len(&best_report) 192 | * CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3); 193 | } else { 194 | printf("No valid CCSDS frames found\n"); 195 | } 196 | 197 | *self = best_report; 198 | 199 | ok = true; 200 | 201 | done: 202 | return ok; 203 | } 204 | -------------------------------------------------------------------------------- /ccsds-guess.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #ifndef _CCSDS_GUESS_H 21 | #define _CCSDS_GUESS_H 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #define CCSDS_TC_BLOCK_SIZE_COUNT 6 29 | #define CCSDS_TC_MINIMUM_BLOCK_LENGTH 223 30 | 31 | struct ccsds_tc_syncword { 32 | const char *syncword; 33 | unsigned int rate_inv; 34 | }; 35 | 36 | const struct ccsds_tc_syncword *ccsds_tc_syncword_lookup( 37 | unsigned int rate_inv); 38 | 39 | /* 40 | * CCSDS enables only 5 different types of block information length, 41 | * of which 3 is forbidden. 42 | */ 43 | struct ccsds_tc_report { 44 | const struct ccsds_tc_syncword *tc_params; 45 | unsigned int histogram[CCSDS_TC_BLOCK_SIZE_COUNT]; 46 | unsigned int count_i; 47 | unsigned int count_q; 48 | }; 49 | 50 | static inline unsigned int 51 | ccsds_tc_report_count_frames(const struct ccsds_tc_report *self) 52 | { 53 | return self->count_i + self->count_q; 54 | } 55 | 56 | static inline unsigned int 57 | ccsds_tc_report_get_candidate_len(const struct ccsds_tc_report *self) 58 | { 59 | unsigned int count, max; 60 | unsigned int i; 61 | 62 | count = 0; 63 | max = 0; 64 | 65 | for (i = 0; i < CCSDS_TC_BLOCK_SIZE_COUNT; ++i) 66 | if (self->histogram[i] > count) { 67 | max = i; 68 | count = self->histogram[i]; 69 | } 70 | 71 | return max; 72 | } 73 | 74 | void ccsds_tc_report_init( 75 | struct ccsds_tc_report *self, 76 | const struct ccsds_tc_syncword *tcs); 77 | 78 | bool ccsds_tc_report_find_frames( 79 | struct ccsds_tc_report *self, 80 | const float complex *x, 81 | unsigned int L, 82 | float SNR, 83 | bool channel); 84 | 85 | bool ccsds_tc_report_find_best( 86 | struct ccsds_tc_report *self, 87 | const float complex *x, 88 | unsigned int L, 89 | float SNR); 90 | 91 | #endif /* _CCSDS_GUESS_H */ 92 | 93 | -------------------------------------------------------------------------------- /ccsds-modem.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "ccsds-modem.h" 32 | #include "ccsds-guess.h" 33 | 34 | #include "lfsr.h" 35 | #include "defs.h" 36 | 37 | #define CCSDS_SCRAMBLER_LENGTH 255 38 | #define CCSDS_FECF_SEED 0xffff 39 | 40 | static bool *g_scrambler = NULL; 41 | 42 | /* 43 | * "Clarification for CCSDS CRC-16 Computation Algorithm", 44 | * code by Jackson Pang, Kenneth Andres and J. Leigh Torgerson 45 | */ 46 | 47 | static uint16_t g_crc_table[256] = { 48 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 49 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 50 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 51 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 52 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 53 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 54 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 55 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 56 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 57 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 58 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 59 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 60 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 61 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 62 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 63 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 64 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 65 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 66 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 67 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 68 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 69 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 70 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 71 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 72 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 73 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 74 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 75 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 76 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 77 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 78 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 79 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, 80 | }; 81 | 82 | /*********************************************************************** 83 | * 84 | * FUNCTION: 85 | * ccsds_fcef_crc16 86 | * 87 | * INPUTS: 88 | * seed - (uint16_t) initial value of check bits. 89 | * buf - (uint8_t *) pointer to the buffer of 90 | * data over which you wish to generate check 91 | * bits. 92 | * len - (int) number to bytes of data in the buffer. 93 | * 94 | * OUTPUTS: 95 | * 96 | * RETURNS: 97 | * - (uint16_t) the checkbits. 98 | * 99 | * EXTERNALLY READ: 100 | * g_crc_table - (uint16_t)[256] the lookup table for the CCITT SDLC 101 | * generator polynomial (local to this module). 102 | * 103 | * EXTERNALLY MODIFIED: 104 | * 105 | * DESCRIPTION: 106 | * This function implements CRC generation with the CCITT SDLC error 107 | * polynomial (X16 + X12 + X5 + 1). You must provide it with an 108 | * initial seed value, a pointer to a data buffer, and the byte length 109 | * of the data buffer. It will return the unsigned 16-bit CRC. 110 | * 111 | * You may use this function to generate a CRC over data in scattered 112 | * storage by making multiple calls to it. Just make sure that you 113 | * pass a seed of 0xFFFF on the first call. On subsequent calls, pass 114 | * a seed containing the return value of the previous call. 115 | */ 116 | 117 | CCSDS_TC_INLINE uint16_t 118 | ccsds_fcef_crc16(uint16_t seed, const uint8_t *buf, unsigned int len) 119 | { 120 | uint16_t crc = seed; 121 | const uint8_t *p = buf; 122 | 123 | while (len--) 124 | crc = g_crc_table[((crc >> 8) ^ *p++) & 0xff] ^ (crc << 8); 125 | 126 | return crc; 127 | } 128 | 129 | const bool * 130 | ccsds_modem_get_scrambler(void) 131 | { 132 | unsigned int taps[] = {0, 3, 5, 7, 8}; 133 | unsigned int i; 134 | bool *result = NULL; 135 | lfsr_t *lfsr = NULL; 136 | 137 | if (g_scrambler == NULL) { 138 | if ((lfsr = lfsr_new(taps, 5)) == NULL) 139 | goto done; 140 | 141 | TRY_ALLOC(result, CCSDS_SCRAMBLER_LENGTH, bool); 142 | 143 | for (i = 0; i < CCSDS_SCRAMBLER_LENGTH; ++i) 144 | result[i] = lfsr_scramble(lfsr, 0); 145 | 146 | g_scrambler = result; 147 | } 148 | 149 | done: 150 | if (lfsr != NULL) 151 | lfsr_destroy(lfsr); 152 | 153 | return g_scrambler; 154 | } 155 | 156 | void 157 | ccsds_modem_destroy(ccsds_modem_t *self) 158 | { 159 | if (self->buffer != NULL) 160 | free(self->buffer); 161 | 162 | if (self->frame_bytes != NULL) 163 | free(self->frame_bytes); 164 | 165 | if (self->syncbuf != NULL) 166 | free(self->syncbuf); 167 | 168 | ccsds_tc_decoder_finalize(&self->decoder); 169 | correlator_finalize(&self->sync_corr); 170 | 171 | free(self); 172 | } 173 | 174 | ccsds_modem_t * 175 | ccsds_modem_new(const struct ccsds_modem_params *params) 176 | { 177 | ccsds_modem_t *new = NULL; 178 | const struct ccsds_tc_syncword *syncword; 179 | struct ccsds_tc_decoder_params tc_params 180 | = ccsds_tc_decoder_params_INITIALIZER; 181 | 182 | if (ccsds_modem_get_scrambler() == NULL) { 183 | fprintf(stderr, "%s: failed to initialize scrambler\n", __FUNCTION__); 184 | return false; 185 | } 186 | 187 | if ((syncword = ccsds_tc_syncword_lookup(params->rate_inv)) == NULL) { 188 | fprintf(stderr, "%s: invalid rate 1/%d\n", __FUNCTION__, params->rate_inv); 189 | goto done; 190 | } 191 | 192 | TRY_CALLOC(new, 1, ccsds_modem_t); 193 | 194 | new->params = *params; 195 | 196 | tc_params.iters = params->iters; 197 | tc_params.rate = params->rate_inv; 198 | tc_params.L_c = CCSDS_TC_EBN0_TO_LC(CCSDS_TC_F2SB(params->EbN0)); 199 | tc_params.block_len = params->block_len; 200 | 201 | new->block_size = tc_params.block_len * CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3; 202 | 203 | if (!ccsds_tc_decoder_init(&new->decoder, &tc_params)) 204 | goto done; 205 | 206 | new->enc_size = ccsds_tc_decoder_get_codeword_size(&new->decoder); 207 | new->sync_len = strlen(syncword->syncword); 208 | new->syncing = true; 209 | 210 | TRY_ALLOC(new->buffer, new->enc_size, softbit_t); 211 | TRY_ALLOC(new->syncbuf, 2 * new->sync_len, float complex); 212 | TRY_ALLOC( 213 | new->frame_bytes, 214 | tc_params.block_len * CCSDS_TC_MINIMUM_BLOCK_LENGTH, 215 | uint8_t); 216 | 217 | if (!correlator_init_from_string(&new->sync_corr, syncword->syncword)) { 218 | fprintf( 219 | stderr, 220 | "%s: failed to initialize correlator for 1/%d syncword\n", 221 | __FUNCTION__, 222 | params->rate_inv); 223 | goto done; 224 | } 225 | 226 | return new; 227 | 228 | done: 229 | if (new != NULL) 230 | ccsds_modem_destroy(new); 231 | 232 | return new; 233 | } 234 | 235 | CCSDS_TC_INLINE unsigned int 236 | ccsds_modem_feed_sync( 237 | ccsds_modem_t *self, 238 | const float complex *__restrict x, 239 | unsigned int L) 240 | { 241 | bool sync_found = false; 242 | unsigned int search_len = 2 * self->sync_len; 243 | unsigned int consumed, p, i, remainder; 244 | int s; 245 | 246 | consumed = L; 247 | p = self->p; 248 | 249 | if (consumed > search_len - p) 250 | consumed = search_len - p; 251 | 252 | for (i = 0; i < consumed; ++i) 253 | self->syncbuf[p++] = x[i]; 254 | 255 | if (p == search_len) { 256 | sync_found = correlator_find_complex( 257 | &self->sync_corr, 258 | self->syncbuf, 259 | search_len, 260 | self->params.sync_snr, 261 | self->params.q_channel); 262 | memmove( 263 | self->syncbuf, 264 | self->syncbuf + self->sync_len, 265 | sizeof(float complex) * self->sync_len); 266 | p = sync_found ? 0 : self->sync_len; 267 | } else { 268 | sync_found = false; 269 | } 270 | 271 | if (sync_found) { 272 | /* SYNC FOUND. Populate softbits */ 273 | self->polarity = correlator_is_reverse(&self->sync_corr) ? +1 : -1; 274 | /* 275 | * If syncword was found in position p, we need to copy the remaining 276 | * search_len - (p + sync_len) softbits to the buffer. We also state 277 | * now that we only consumed the first p + sync_len softbits. 278 | */ 279 | 280 | remainder = correlator_get_pos(&self->sync_corr) + self->sync_len; 281 | i = remainder; 282 | p = 0; 283 | 284 | /* Note that search_len is never bigger than the buffer */ 285 | while (i < search_len) { 286 | s = 2 * (int) g_scrambler[p % CCSDS_SCRAMBLER_LENGTH] - 1; 287 | 288 | if (self->params.q_channel) 289 | self->buffer[p++] = s * CCSDS_TC_F2SB( 290 | self->polarity * cimag(self->syncbuf[i++])); 291 | else 292 | self->buffer[p++] = s * CCSDS_TC_F2SB( 293 | self->polarity * creal(self->syncbuf[i++])); 294 | } 295 | 296 | self->syncing = false; 297 | } 298 | 299 | self->p = p; 300 | 301 | return consumed; 302 | } 303 | 304 | CCSDS_TC_INLINE unsigned int 305 | ccsds_modem_feed_payload( 306 | ccsds_modem_t *self, 307 | const float complex *__restrict x, 308 | unsigned int L) 309 | { 310 | unsigned int consumed, i, p; 311 | int s; 312 | 313 | /* STATE 1: DECODING. Decide how much we should consume beforehad */ 314 | consumed = L; 315 | p = self->p; 316 | 317 | if (consumed > self->enc_size - p) 318 | consumed = self->enc_size - p; 319 | 320 | if (self->params.q_channel) { 321 | for (i = 0; i < consumed; ++i, ++p) { 322 | s = 2 * (int) g_scrambler[p % CCSDS_SCRAMBLER_LENGTH] - 1; 323 | self->buffer[p] = s * CCSDS_TC_F2SB(self->polarity * cimag(x[i])); 324 | } 325 | } else { 326 | for (i = 0; i < consumed; ++i, ++p) { 327 | s = 2 * (int) g_scrambler[p % CCSDS_SCRAMBLER_LENGTH] - 1; 328 | self->buffer[p] = s * CCSDS_TC_F2SB(self->polarity * creal(x[i])); 329 | } 330 | } 331 | 332 | if (p == self->enc_size) { 333 | /* FULL FRAME! */ 334 | ccsds_tc_decoder_feed_block(&self->decoder, self->buffer); 335 | self->y = ccsds_tc_decoder_get_output(&self->decoder); 336 | p = 0; 337 | self->syncing = true; 338 | } 339 | 340 | self->p = p; 341 | 342 | return consumed; 343 | } 344 | 345 | CCSDS_TC_INLINE bool 346 | ccsds_modem_try_extract_frame(ccsds_modem_t *self) 347 | { 348 | unsigned int i; 349 | uint16_t crc; 350 | unsigned int frame_len = self->block_size >> 3; 351 | 352 | for (i = 0; i < self->block_size; ++i) 353 | if ((i & 7) == 0) 354 | self->frame_bytes[i >> 3] = (self->y[i] > 0) << 7; 355 | else 356 | self->frame_bytes[i >> 3] |= (self->y[i] > 0) << (7 - (i & 7)); 357 | 358 | /* CCSDS frames are big endian */ 359 | crc = ntohs(*(const uint16_t *) &self->frame_bytes[frame_len - 2]); 360 | 361 | /* Check FECF */ 362 | return ccsds_fcef_crc16( 363 | CCSDS_FECF_SEED, 364 | self->frame_bytes, 365 | frame_len - 2) == crc; 366 | } 367 | 368 | bool 369 | ccsds_modem_feed( 370 | ccsds_modem_t *self, 371 | const float complex *__restrict x, 372 | unsigned int L) 373 | { 374 | bool have_frame = false; 375 | unsigned int consumed; 376 | 377 | while (L > 0) { 378 | if (self->syncing) { 379 | consumed = ccsds_modem_feed_sync(self, x, L); 380 | } else { 381 | consumed = ccsds_modem_feed_payload(self, x, L); 382 | have_frame = have_frame || self->syncing; 383 | } 384 | 385 | x += consumed; 386 | L -= consumed; 387 | } 388 | 389 | /* Something that looks like a frame? */ 390 | if (have_frame) 391 | have_frame = ccsds_modem_try_extract_frame(self); 392 | 393 | return have_frame; 394 | } 395 | 396 | bool 397 | ccsds_modem_process_area( 398 | ccsds_modem_t *self, 399 | const float complex *__restrict x, 400 | unsigned int L) 401 | { 402 | float polarity = 1, f; 403 | const bool *scrambler = NULL; 404 | unsigned int p = 0, i, q; 405 | unsigned int block_size; 406 | unsigned int enc_size; 407 | unsigned int sync_step; 408 | unsigned int frame_size; 409 | bool syncing = true; 410 | bool ok = false; 411 | 412 | enc_size = self->enc_size; 413 | sync_step = self->sync_len; 414 | frame_size = sync_step + enc_size + 4 * self->params.rate_inv; 415 | 416 | if ((scrambler = ccsds_modem_get_scrambler()) == NULL) 417 | goto done; 418 | 419 | /* Decoding loop */ 420 | while (p < L - frame_size) { 421 | if (syncing) { 422 | if (correlator_find_complex( 423 | &self->sync_corr, 424 | x + p, 425 | 2 * sync_step, 426 | self->params.sync_snr, 427 | self->params.q_channel)) { 428 | polarity = correlator_is_reverse(&self->sync_corr) ? -1 : +1; 429 | p += correlator_get_pos(&self->sync_corr); 430 | syncing = false; 431 | } 432 | 433 | p += sync_step; 434 | } else { 435 | q = 0; 436 | if (self->params.q_channel) { 437 | for (i = 0; i < enc_size; ++i) { 438 | f = 2 * (int) scrambler[q++ % CCSDS_SCRAMBLER_LENGTH] - 1; 439 | self->buffer[i] = CCSDS_TC_F2SB(-polarity * cimag(x[p + i]) * f); 440 | } 441 | } else { 442 | for (i = 0; i < enc_size; ++i) { 443 | f = 2 * (int) scrambler[q++ % CCSDS_SCRAMBLER_LENGTH] - 1; 444 | self->buffer[i] = CCSDS_TC_F2SB(-polarity * creal(x[p + i]) * f); 445 | } 446 | } 447 | 448 | ccsds_tc_decoder_feed_block(&self->decoder, self->buffer); 449 | self->y = ccsds_tc_decoder_get_output(&self->decoder); 450 | 451 | /* TODO: Trigger action */ 452 | p += enc_size; 453 | syncing = true; 454 | } 455 | } 456 | 457 | ok = true; 458 | 459 | done: 460 | return ok; 461 | } 462 | 463 | -------------------------------------------------------------------------------- /ccsds-modem.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #ifndef _CCSDS_MODEM_H 21 | #define _CCSDS_MODEM_H 22 | 23 | #include "ccsds-tc.h" 24 | #include "correlator.h" 25 | 26 | #include 27 | 28 | #define CCSDS_MODEM_DEFAULT_RATE 6 29 | #define CCSDS_MODEM_DEFAULT_BLOCK_LEN CCSDS_TC_BLOCK_LENGTH_1115 30 | #define CCSDS_MODEM_DEFAULT_ITERS 1 31 | #define CCSDS_MODEM_DEFAULT_SYNC_SNR 80. 32 | #define CCSDS_MODEM_DEFAULT_EBN0 0.6310 33 | #define CCSDS_MODEM_DEFAULT_CHANNEL true 34 | 35 | struct ccsds_modem_params { 36 | unsigned int rate_inv; 37 | enum ccsds_tc_decoder_block_length block_len; 38 | unsigned int iters; 39 | float sync_snr; 40 | float EbN0; 41 | bool q_channel; 42 | }; 43 | 44 | #define ccsds_modem_params_INITIALIZER \ 45 | { \ 46 | CCSDS_MODEM_DEFAULT_RATE, \ 47 | CCSDS_MODEM_DEFAULT_BLOCK_LEN, \ 48 | CCSDS_MODEM_DEFAULT_ITERS, \ 49 | CCSDS_MODEM_DEFAULT_SYNC_SNR, \ 50 | CCSDS_MODEM_DEFAULT_EBN0, \ 51 | CCSDS_MODEM_DEFAULT_CHANNEL \ 52 | } 53 | 54 | struct ccsds_modem { 55 | struct ccsds_modem_params params; 56 | correlator_t sync_corr; 57 | ccsds_tc_decoder_t decoder; 58 | bool syncing; 59 | 60 | /* Precalculated variables */ 61 | softbit_t *buffer; 62 | float complex *syncbuf; 63 | const softbit_t *y; 64 | unsigned int p, q; 65 | unsigned int sync_len; 66 | float polarity; 67 | 68 | unsigned int frame_size; 69 | unsigned int block_size; 70 | unsigned int enc_size; 71 | uint8_t *frame_bytes; 72 | }; 73 | 74 | typedef struct ccsds_modem ccsds_modem_t; 75 | 76 | ccsds_modem_t *ccsds_modem_new(const struct ccsds_modem_params *params); 77 | 78 | bool ccsds_modem_feed( 79 | ccsds_modem_t *self, 80 | const float complex *x, 81 | unsigned int L); 82 | 83 | CCSDS_TC_INLINE const softbit_t * 84 | ccsds_modem_get_frame_bits(const ccsds_modem_t *self) 85 | { 86 | return self->y; 87 | } 88 | 89 | CCSDS_TC_INLINE unsigned int 90 | ccsds_modem_get_block_length(const ccsds_modem_t *self) 91 | { 92 | return self->block_size; 93 | } 94 | 95 | CCSDS_TC_INLINE float 96 | ccsds_modem_get_error_rate(const ccsds_modem_t *self) 97 | { 98 | unsigned int i, count; 99 | unsigned int block_size = ccsds_modem_get_block_length(self); 100 | bool b1, b2; 101 | 102 | count = 0; 103 | for (i = 0; i < block_size; ++i) { 104 | b2 = self->y[i] > 0; 105 | b1 = self->buffer[i * self->params.rate_inv] > 0; 106 | 107 | if (b1 != b2) 108 | ++count; 109 | } 110 | 111 | return (float) count / (float) block_size; 112 | } 113 | 114 | CCSDS_TC_INLINE const uint8_t * 115 | ccsds_modem_get_frame_data(const ccsds_modem_t *self) 116 | { 117 | return self->frame_bytes; 118 | } 119 | 120 | CCSDS_TC_INLINE unsigned int 121 | ccsds_modem_get_frame_length(const ccsds_modem_t *self) 122 | { 123 | return self->block_size >> 3; 124 | } 125 | 126 | void ccsds_modem_destroy(ccsds_modem_t *self); 127 | 128 | #endif /* _CCSDS_MODEM */ 129 | -------------------------------------------------------------------------------- /ccsds-tc.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #include "ccsds-tc.h" 21 | #include 22 | #include 23 | #include 24 | 25 | #define LOG2 CCSDS_TC_F2SB(0.69314718055995) 26 | #define MAXSTAR_LIN_LIMIT CCSDS_TC_F2SB(2 * 0.69314718055995) 27 | #define BIT2SOFT(b) (CCSDS_TC_SCALE * (2 * (softbit_t) (b) - 1)) 28 | #define ALLOC(where, N, what) \ 29 | if ((where = malloc(N * sizeof(what))) == NULL) \ 30 | goto fail 31 | 32 | 33 | /******************* BCJR convolutional decoder *********************/ 34 | #ifdef CCSDS_TC_NO_MAXSTAR 35 | # define ccsds_tc_softbit_maxstar CCSDS_TC_MAX 36 | #else 37 | CCSDS_TC_INLINE softbit_t 38 | ccsds_tc_softbit_maxstar(softbit_t a, softbit_t b) 39 | { 40 | softbit_t diff; 41 | 42 | if (CCSDS_TC_IMPOSSIBLE(a) || CCSDS_TC_IMPOSSIBLE(b)) 43 | return CCSDS_TC_MAX(a, b); 44 | 45 | if (a > b) { 46 | diff = a - b; 47 | if (diff > MAXSTAR_LIN_LIMIT) 48 | return a; 49 | else 50 | return a + LOG2 - diff / 2; 51 | } else { 52 | diff = b - a; 53 | if (diff > MAXSTAR_LIN_LIMIT) 54 | return b; 55 | else 56 | return b + LOG2 - diff / 2; 57 | } 58 | } 59 | #endif /* CCSDS_TC_NO_MAXSTAR */ 60 | 61 | /* This is derived from a Taylor expansion of 62 | fmaxf(a, b) + log(1 + exp(-fabs(a-b))) */ 63 | 64 | CCSDS_TC_INLINE state_t 65 | ccsds_tc_bcjr_state_dotprod(state_t a, state_t b) 66 | { 67 | #ifdef __GNUC__ 68 | return __builtin_parity(a & b); 69 | #else 70 | /* TODO: generate at compile time */ 71 | return 72 | ((a >> 0) & (b >> 0) & 1) 73 | ^ ((a >> 1) & (b >> 1) & 1) 74 | ^ ((a >> 2) & (b >> 2) & 1) 75 | ^ ((a >> 3) & (b >> 3) & 1) 76 | ^ ((a >> 4) & (b >> 4) & 1); 77 | #endif 78 | } 79 | 80 | CCSDS_TC_INLINE state_t 81 | ccsds_tc_bcjr_state_next(state_t a, state_t poly_b, bool next) 82 | { 83 | next ^= ccsds_tc_bcjr_state_dotprod(a, poly_b); 84 | 85 | return CCSDS_TC_STATE_MASK & ((a << 1) | (next & 1)); 86 | } 87 | 88 | CCSDS_TC_INLINE bool 89 | ccsds_tc_bcjr_connect_state( 90 | ccsds_tc_bcjr_ctx_t *self, 91 | state_t prev, 92 | state_t next, 93 | bool b) 94 | { 95 | /* Forward connection. Trivial. */ 96 | self->S_next[(prev << 1) + b] = next; 97 | 98 | /* 99 | * Backward connection. Not so trivial. We check whether 100 | * the previous state is set to "unused" and update the 101 | * entry accordingly 102 | */ 103 | 104 | if (next == 0) { 105 | if (b) 106 | self->S_last_1 = prev; 107 | else 108 | self->S_last_0 = prev; 109 | } 110 | 111 | if (self->S_prev[(next << 1) + 0] == CCSDS_TC_STATE_UNUSED) { 112 | self->S_prev[(next << 1) + 0] = prev; 113 | } else if (self->S_prev[(next << 1) + 1] == CCSDS_TC_STATE_UNUSED) { 114 | self->S_prev[(next << 1) + 1] = prev; 115 | } else { 116 | fprintf(stderr, "critical: more than 2 previous states?\n"); 117 | return false; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | void 124 | ccsds_tc_bcjr_ctx_finalize(ccsds_tc_bcjr_ctx_t *self) 125 | { 126 | if (self->S_next != NULL) 127 | free(self->S_next); 128 | 129 | if (self->S_prev != NULL) 130 | free(self->S_prev); 131 | 132 | if (self->hv != NULL) 133 | free(self->hv); 134 | 135 | if (self->rv != NULL) 136 | free(self->rv); 137 | 138 | if (self->astar != NULL) 139 | free(self->astar); 140 | 141 | if (self->bstar != NULL) 142 | free(self->bstar); 143 | 144 | if (self->gstar_dir != NULL) 145 | free(self->gstar_dir); 146 | 147 | if (self->gstar_rev != NULL) 148 | free(self->gstar_rev); 149 | 150 | if (self->min_lh_buf != NULL) 151 | free(self->min_lh_buf); 152 | } 153 | 154 | bool 155 | ccsds_tc_bcjr_ctx_init( 156 | ccsds_tc_bcjr_ctx_t *self, 157 | const poly_t *poly_f, 158 | poly_t poly_b, 159 | unsigned int n, 160 | unsigned int h) 161 | { 162 | unsigned int i, j; 163 | unsigned int cwnb; /* Codeword number and bit */ 164 | unsigned int cwnb_0, cwnb_1; 165 | bool b; 166 | state_t next0, next1; 167 | softbit_t lh; 168 | bool ok = false; 169 | 170 | memset(self, 0, sizeof(ccsds_tc_bcjr_ctx_t)); 171 | 172 | poly_b >>= 1; /* Discard rightmost bit */ 173 | 174 | self->h = h; 175 | self->n = n; 176 | self->poly_b = poly_b; 177 | self->length = h + CCSDS_TC_STATE_BITS ; 178 | 179 | /* Allocate data */ 180 | ALLOC(self->hv, 2 * CCSDS_TC_STATE_NUM, state_t); 181 | ALLOC(self->S_next, 2 * self->n * CCSDS_TC_STATE_NUM, state_t); 182 | ALLOC(self->S_prev, 2 * self->n * CCSDS_TC_STATE_NUM, state_t); 183 | ALLOC(self->rv, 2 * self->length * CCSDS_TC_STATE_NUM, softbit_t); 184 | ALLOC(self->astar, self->length * CCSDS_TC_STATE_NUM, softbit_t); 185 | ALLOC(self->bstar, (self->length + 1) * CCSDS_TC_STATE_NUM, softbit_t); 186 | ALLOC(self->gstar_dir, 2 * self->length * CCSDS_TC_STATE_NUM, softbit_t); 187 | ALLOC(self->gstar_rev, 2 * self->length * CCSDS_TC_STATE_NUM, softbit_t); 188 | ALLOC(self->min_lh_buf, 2 * CCSDS_TC_STATE_NUM, softbit_t); 189 | 190 | for (j = 0; j < 2 * self->length * CCSDS_TC_STATE_NUM; ++j) 191 | self->gstar_dir[j] = self->gstar_rev[j] = CCSDS_TC_MIN_LH; 192 | 193 | for (j = 0; j < 2 * CCSDS_TC_STATE_NUM; ++j) 194 | self->min_lh_buf[j] = CCSDS_TC_MIN_LH; 195 | 196 | memset(self->hv, 0, 2 * CCSDS_TC_STATE_NUM * sizeof(state_t)); 197 | memset(self->rv, 0, 2 * self->length * CCSDS_TC_STATE_NUM * sizeof(softbit_t)); 198 | 199 | /* Mark all previous states as unused */ 200 | for (j = 0; j < CCSDS_TC_STATE_NUM; ++j) { 201 | self->S_prev[(j << 1) + 0] = CCSDS_TC_STATE_UNUSED; 202 | self->S_prev[(j << 1) + 1] = CCSDS_TC_STATE_UNUSED; 203 | } 204 | 205 | /* Initialize path metrics */ 206 | for (j = 0; j < CCSDS_TC_STATE_NUM; ++j) { 207 | lh = j == 0 ? CCSDS_TC_MAX_LH : CCSDS_TC_MIN_LH; 208 | self->astar[j] = lh; 209 | self->bstar[CCSDS_TC_STATE_NUM * (self->length) + j] = lh; 210 | } 211 | 212 | /* Initialize codeword table AND trellis connections. -1 is zero. */ 213 | for (j = 0; j < CCSDS_TC_STATE_NUM; ++j) { 214 | b = ccsds_tc_bcjr_state_dotprod(j, poly_b); 215 | next0 = ccsds_tc_bcjr_state_next(j, poly_b, false); 216 | next1 = ccsds_tc_bcjr_state_next(j, poly_b, true); 217 | 218 | /* Connect state j to next if input is 0 */ 219 | if (!ccsds_tc_bcjr_connect_state( 220 | self, 221 | j, 222 | next0, 223 | false)) 224 | goto fail; 225 | 226 | /* Connect state j to next if input is 1 */ 227 | if (!ccsds_tc_bcjr_connect_state( 228 | self, 229 | j, 230 | next1, 231 | true)) 232 | goto fail; 233 | 234 | for (i = 0; i < n; ++i) 235 | self->hv[(j << 1) + 0] 236 | |= ccsds_tc_bcjr_state_dotprod(poly_f[i], (j << 1) ^ b ^ false) << i; 237 | for (i = 0; i < n; ++i) 238 | self->hv[(j << 1) + 1] 239 | |= ccsds_tc_bcjr_state_dotprod(poly_f[i], (j << 1) ^ b ^ true) << i; 240 | } 241 | 242 | ok = true; 243 | 244 | fail: 245 | if (!ok) 246 | ccsds_tc_bcjr_ctx_finalize(self); 247 | 248 | return ok; 249 | } 250 | 251 | CCSDS_TC_INLINE softbit_t 252 | ccsds_tc_bcjr_ctx_semisoft_dotprod_2(const softbit_t *__restrict r, state_t hv) 253 | { 254 | switch (hv) { 255 | case 0: 256 | return -(r[0] + r[1]); 257 | case 1: 258 | return r[0] - r[1]; 259 | case 2: 260 | return r[1] - r[0]; 261 | case 3: 262 | return r[1] + r[0]; 263 | } 264 | 265 | return 0; 266 | } 267 | CCSDS_TC_INLINE softbit_t 268 | ccsds_tc_bcjr_ctx_semisoft_dotprod(const softbit_t *__restrict r, state_t hv, unsigned int L) 269 | { 270 | softbit_t accum = 0; 271 | 272 | switch (L) { 273 | case 2: 274 | return ccsds_tc_bcjr_ctx_semisoft_dotprod_2(r, hv); 275 | 276 | case 4: 277 | return ccsds_tc_bcjr_ctx_semisoft_dotprod_2(r, hv & 3) 278 | + ccsds_tc_bcjr_ctx_semisoft_dotprod_2(r + 2, (hv >> 2) & 3); 279 | } 280 | 281 | while (L-- != 0) { 282 | if (hv & 1) 283 | accum += *r++; 284 | else 285 | accum -= *r++; 286 | 287 | hv >>= 1; 288 | } 289 | return accum; 290 | } 291 | 292 | 293 | CCSDS_TC_INLINE void 294 | ccsds_tc_bcjr_ctx_set_gamma( 295 | ccsds_tc_bcjr_ctx_t *self, 296 | unsigned int j, /* position */ 297 | state_t state, /* previous state */ 298 | bool b, /* next bit */ 299 | const softbit_t *__restrict L_u) /* received codeword */ 300 | { 301 | unsigned int sndx = (state << 1) + b; 302 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM << 1; 303 | unsigned int dest = pos_off + sndx; 304 | state_t next = self->S_next[sndx]; 305 | state_t next2 = next << 1; 306 | 307 | /* 308 | * Gamma star is already a posterior log-probability 309 | * that the received codeword corresponds to a given bit 310 | * given the current state. It is an expression of the form: 311 | * 312 | * 1/2 (bit * L(bit) + CW * L(CW)) 313 | * 314 | * In this expression we basically combine prior information 315 | * of the bit (L(bit)) + the soft information of the received 316 | * codeword. This is dot-multiplied by the bit and expected 317 | * codeword of the current branch. 318 | */ 319 | 320 | /* We only set gamma in the intermediate cases */ 321 | const softbit_t gstar = (j < self->h 322 | ? (BIT2SOFT(b) / CCSDS_TC_SSQRT) * (L_u[j] / (2 * CCSDS_TC_SSQRT)) : 0) 323 | + self->rv[dest]; 324 | 325 | /* 326 | * For each j, gamma is actually a multivaluated function 327 | * on the state number. Since we don't actually need 328 | * STATE_NUM x STATE_NUM gammas, as many of the state connections 329 | * are forbidden, we provide a sparse representation in 330 | * which the row-to-cols and col-to-rows view are stored. 331 | * We can do this because we know that, for each departure 332 | * state there are 2 possible arrival states (gamma_dir) and 333 | * for each arrival states, there are 2 possible departure 334 | * states (gamma_rev). 335 | */ 336 | 337 | /* gstar_dir*: gamma* row-to-cols view */ 338 | self->gstar_dir[dest] = gstar; 339 | 340 | /* 341 | * gstar_rev: gamma* col-to-rows view. Now this is 342 | * the trick: we are going to enforce the SAME indexing 343 | * as with S_prev, so we know that the first of the 344 | * two gamma* arriving to state "next" refers to the 345 | * the first of the previous states to "next" 346 | */ 347 | 348 | self->gstar_rev[pos_off + next2 + (self->S_prev[next2] != state)] = gstar; 349 | } 350 | 351 | 352 | CCSDS_TC_INLINE void 353 | ccsds_tc_bcjr_ctx_set_gamma_bulk( 354 | ccsds_tc_bcjr_ctx_t *self, 355 | const softbit_t *__restrict L_u) 356 | { 357 | unsigned int p = CCSDS_TC_STATE_NUM << 1; 358 | unsigned int i, j; 359 | 360 | #pragma GCC unroll 2 361 | for (j = 1; j < self->h; ++j) { 362 | #pragma GCC unroll 32 363 | for (i = 0; i < (CCSDS_TC_STATE_NUM << 1); ++i, ++p) { 364 | const state_t next = self->S_next[i]; 365 | const softbit_t gstar = 366 | (BIT2SOFT(i & 1) / CCSDS_TC_SSQRT) * (L_u[j] / (2 * CCSDS_TC_SSQRT)) 367 | + self->rv[p]; 368 | const unsigned int rev_p = 369 | ((j * CCSDS_TC_STATE_NUM + next) << 1) + (self->S_prev[next << 1] != (i >> 1)); 370 | self->gstar_dir[p] = gstar; 371 | self->gstar_rev[rev_p] = gstar; 372 | } 373 | } 374 | 375 | #pragma GCC unroll 2 376 | for (j = self->h; j < self->length - 1; ++j) { 377 | #pragma GCC unroll 32 378 | for (i = 0; i < (CCSDS_TC_STATE_NUM << 1); ++i, ++p) { 379 | const state_t next = self->S_next[i]; 380 | const softbit_t gstar = self->rv[p]; 381 | const unsigned int rev_p = 382 | ((j * CCSDS_TC_STATE_NUM + next) << 1) + (self->S_prev[next << 1] != (i >> 1)); 383 | self->gstar_dir[p] = gstar; 384 | self->gstar_rev[rev_p] = gstar; 385 | } 386 | } 387 | } 388 | 389 | CCSDS_TC_INLINE void 390 | ccsds_tc_bcjr_ctx_set_alpha( 391 | ccsds_tc_bcjr_ctx_t *self, 392 | unsigned int j, /* next position */ 393 | state_t next) /* next state */ 394 | { 395 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM; 396 | unsigned int prev_off = pos_off - CCSDS_TC_STATE_NUM; 397 | unsigned int gprv_off = prev_off << 1; 398 | unsigned int i0 = (next << 1) + 0; 399 | unsigned int i1 = i0 + 1; 400 | softbit_t astar; 401 | 402 | /* There are only two previous states */ 403 | astar = 404 | ccsds_tc_softbit_maxstar( 405 | CCSDS_TC_SAFE_SUM( 406 | self->gstar_rev[gprv_off + i0], 407 | self->astar[prev_off + self->S_prev[i0]]), 408 | CCSDS_TC_SAFE_SUM( 409 | self->gstar_rev[gprv_off + i1], 410 | self->astar[prev_off + self->S_prev[i1]])); 411 | 412 | self->astar[pos_off + next] = astar; 413 | } 414 | 415 | CCSDS_TC_INLINE void 416 | ccsds_tc_bcjr_ctx_set_beta( 417 | ccsds_tc_bcjr_ctx_t *self, 418 | unsigned int j, /* curr position */ 419 | state_t prev) /* curr state */ 420 | { 421 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM; 422 | unsigned int next_off = pos_off + CCSDS_TC_STATE_NUM; 423 | unsigned int gpos_off = pos_off << 1; 424 | unsigned int i0 = (prev << 1) + 0; 425 | unsigned int i1 = i0 + 1; 426 | softbit_t bstar; 427 | 428 | /* There are only two next states */ 429 | bstar = 430 | ccsds_tc_softbit_maxstar( 431 | CCSDS_TC_SAFE_SUM( 432 | self->gstar_dir[gpos_off + i0], 433 | self->bstar[next_off + self->S_next[i0]]), 434 | CCSDS_TC_SAFE_SUM( 435 | self->gstar_dir[gpos_off + i1], 436 | self->bstar[next_off + self->S_next[i1]])); 437 | 438 | self->bstar[pos_off + prev] = bstar; 439 | } 440 | 441 | CCSDS_TC_INLINE softbit_t 442 | ccsds_tc_bcjr_ctx_get_Lu(ccsds_tc_bcjr_ctx_t *self, unsigned int j) 443 | { 444 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM; 445 | unsigned int pos_off2 = pos_off << 1; 446 | unsigned int next_off = pos_off + CCSDS_TC_STATE_NUM; 447 | unsigned int i, state_off; 448 | softbit_t L0, L1; 449 | 450 | L0 = CCSDS_TC_MIN_LH; 451 | L1 = CCSDS_TC_MIN_LH; 452 | 453 | state_off = 0; 454 | #pragma GCC unroll 16 455 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i) { 456 | L0 = ccsds_tc_softbit_maxstar( 457 | CCSDS_TC_SAFE_SUM( 458 | self->bstar[next_off + self->S_next[state_off]], 459 | CCSDS_TC_SAFE_SUM( 460 | self->gstar_dir[pos_off2 + state_off], 461 | self->astar[pos_off + i])), 462 | L0); 463 | state_off += 2; 464 | } 465 | 466 | state_off = 1; 467 | 468 | #pragma GCC unroll 16 469 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i) { 470 | L1 = ccsds_tc_softbit_maxstar( 471 | CCSDS_TC_SAFE_SUM( 472 | self->bstar[next_off + self->S_next[state_off]], 473 | CCSDS_TC_SAFE_SUM( 474 | self->gstar_dir[pos_off2 + state_off], 475 | self->astar[pos_off + i])), 476 | L1); 477 | state_off += 2; 478 | } 479 | 480 | return L1 - L0; 481 | } 482 | 483 | CCSDS_TC_INLINE softbit_t 484 | ccsds_tc_bcjr_ctx_rebuild_rv_cache( 485 | ccsds_tc_bcjr_ctx_t *self, 486 | const softbit_t *__restrict x, 487 | softbit_t L_c) 488 | { 489 | unsigned int i, j, p = 0; 490 | unsigned int q = 0; 491 | 492 | #pragma GCC unroll 2 493 | for (j = 0; j < self->length; ++j) { 494 | #pragma GCC unroll 32 495 | for (i = 0; i < (CCSDS_TC_STATE_NUM << 1); ++i, ++p) { 496 | self->rv[p] 497 | = (L_c / (2 * CCSDS_TC_SSQRT)) 498 | * (ccsds_tc_bcjr_ctx_semisoft_dotprod( 499 | &x[q], 500 | self->hv[i], 501 | self->n) / CCSDS_TC_SSQRT); 502 | } 503 | q += self->n; 504 | } 505 | 506 | self->rv_cached = true; 507 | } 508 | 509 | bool 510 | ccsds_tc_bcjr_ctx_decode( 511 | ccsds_tc_bcjr_ctx_t *self, 512 | const softbit_t *__restrict x, 513 | const softbit_t *__restrict L_u, 514 | softbit_t *y, 515 | softbit_t L_c, 516 | const int *__restrict permute_out) 517 | { 518 | unsigned int i, j, i0, i1, off = 0; 519 | softbit_t lh, L0, L1; 520 | unsigned int cwnb; 521 | unsigned int cwnb_0, cwnb_1; 522 | bool ok = false; 523 | 524 | if (!self->rv_cached) 525 | ccsds_tc_bcjr_ctx_rebuild_rv_cache(self, x, L_c); 526 | 527 | ccsds_tc_bcjr_ctx_set_gamma(self, 0, 0, false, L_u); 528 | ccsds_tc_bcjr_ctx_set_gamma(self, 0, 0, true, L_u); 529 | ccsds_tc_bcjr_ctx_set_gamma( 530 | self, 531 | self->length - 1, 532 | self->S_last_0, 533 | false, 534 | L_u); 535 | ccsds_tc_bcjr_ctx_set_gamma( 536 | self, 537 | self->length - 1, 538 | self->S_last_1, 539 | true, 540 | L_u); 541 | 542 | ccsds_tc_bcjr_ctx_set_gamma_bulk(self, L_u); 543 | 544 | /* Set path metrics (forward) */ 545 | #pragma GCC unroll 2 546 | for (j = 1; j < self->length; ++j) 547 | #pragma GCC unroll 16 548 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i) 549 | ccsds_tc_bcjr_ctx_set_alpha(self, j, i); 550 | 551 | /* Set path metrics (backward) */ 552 | #pragma GCC unroll 2 553 | for (j = 1; j < self->length; ++j) 554 | #pragma GCC unroll 16 555 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i) 556 | ccsds_tc_bcjr_ctx_set_beta(self, self->length - j, i); 557 | 558 | /* Compute bit likelihoods and finish */ 559 | if (permute_out != NULL) { 560 | #pragma GCC unroll 2 561 | for (j = 0; j < self->h; ++j) 562 | y[j] = ccsds_tc_bcjr_ctx_get_Lu(self, permute_out[j]); 563 | #pragma GCC unroll 22 564 | for (j = self->h; j < self->length; ++j) 565 | y[j] = 0; 566 | } else { 567 | #pragma GCC unroll 2 568 | for (j = 0; j < self->length; ++j) 569 | y[j] = ccsds_tc_bcjr_ctx_get_Lu(self, j); 570 | } 571 | 572 | ok = true; 573 | 574 | done: 575 | return ok; 576 | } 577 | 578 | /*********************** Full CCSDS Turbocode *******************/ 579 | static int * 580 | ccsds_tc_decoder_make_permutator(int k2) 581 | { 582 | int *pi = NULL; 583 | int s, m, i, j, t, q, c; 584 | int p[] = {31, 37, 43, 47, 53, 59, 61, 67}; 585 | 586 | if ((pi = malloc(sizeof(int) * k2 * 8)) == NULL) 587 | return NULL; 588 | 589 | for (s = 1; s <= k2 * 8; ++s) { 590 | m = (s - 1) & 1; 591 | i = (s - 1) / (2 * k2); 592 | j = (s - 1) / 2 - i * k2; 593 | t = (19 * i + 1) % 4; 594 | q = (t % 8) + 1; 595 | c = (p[q - 1] * j + 21 * m) % k2; 596 | pi[s - 1] = 2 * (t + c * 4 + 1) - m - 1; 597 | } 598 | 599 | return pi; 600 | } 601 | 602 | /* RATE 1/2: Handmade puncturing */ 603 | static void 604 | ser2par_sb_2(ccsds_tc_decoder_t *self, const softbit_t *__restrict r) 605 | { 606 | unsigned int i; 607 | 608 | for (i = 0; i < self->terminated_len; ++i) { 609 | self->x1[2 * i + 0] = r[2 * i + 0]; 610 | self->x1[2 * i + 1] = i & 1 ? CCSDS_TC_F2SB(0) : r[2 * i + 1]; 611 | self->x2[i] = i & 1 ? r[2 * i + 1] : CCSDS_TC_F2SB(0); 612 | } 613 | } 614 | 615 | static void 616 | ser2par_float_2(ccsds_tc_decoder_t *self, const float *__restrict r) 617 | { 618 | unsigned int i; 619 | 620 | for (i = 0; i < self->terminated_len; ++i) { 621 | self->x1[2 * i + 0] = CCSDS_TC_F2SB(r[2 * i + 0]); 622 | self->x1[2 * i + 1] = i & 1 ? CCSDS_TC_F2SB(0) : CCSDS_TC_F2SB(r[2 * i + 1]); 623 | self->x2[i] = i & 1 ? CCSDS_TC_F2SB(r[2 * i + 1]) : CCSDS_TC_F2SB(0); 624 | } 625 | } 626 | 627 | /* RATE 1/3 */ 628 | static void 629 | ser2par_sb_3(ccsds_tc_decoder_t *self, const softbit_t *__restrict r) 630 | { 631 | unsigned int i; 632 | 633 | for (i = 0; i < self->terminated_len; ++i) { 634 | self->x1[2 * i + 0] = r[3 * i + 0]; 635 | self->x1[2 * i + 1] = r[3 * i + 1]; 636 | self->x2[i] = r[3 * i + 2]; 637 | } 638 | } 639 | 640 | static void 641 | ser2par_float_3(ccsds_tc_decoder_t *self, const float *__restrict r) 642 | { 643 | unsigned int i; 644 | 645 | for (i = 0; i < self->terminated_len; ++i) { 646 | self->x1[2 * i + 0] = CCSDS_TC_F2SB(r[3 * i + 0]); 647 | self->x1[2 * i + 1] = CCSDS_TC_F2SB(r[3 * i + 1]); 648 | self->x2[i] = CCSDS_TC_F2SB(r[3 * i + 2]); 649 | } 650 | } 651 | 652 | /* RATE 1/4 */ 653 | static void 654 | ser2par_sb_4(ccsds_tc_decoder_t *self, const softbit_t *__restrict r) 655 | { 656 | unsigned int i; 657 | 658 | for (i = 0; i < self->terminated_len; ++i) { 659 | self->x1[3 * i + 0] = r[4 * i + 0]; 660 | self->x1[3 * i + 1] = r[4 * i + 1]; 661 | self->x1[3 * i + 2] = r[4 * i + 2]; 662 | self->x2[i] = r[4 * i + 3]; 663 | } 664 | } 665 | 666 | static void 667 | ser2par_float_4(ccsds_tc_decoder_t *self, const float *__restrict r) 668 | { 669 | unsigned int i; 670 | 671 | for (i = 0; i < self->terminated_len; ++i) { 672 | self->x1[3 * i + 0] = CCSDS_TC_F2SB(r[4 * i + 0]); 673 | self->x1[3 * i + 1] = CCSDS_TC_F2SB(r[4 * i + 1]); 674 | self->x1[3 * i + 2] = CCSDS_TC_F2SB(r[4 * i + 2]); 675 | self->x2[i] = CCSDS_TC_F2SB(r[4 * i + 3]); 676 | } 677 | } 678 | 679 | /* RATE 1/6 */ 680 | static void 681 | ser2par_sb_6(ccsds_tc_decoder_t *self, const softbit_t *__restrict r) 682 | { 683 | unsigned int i; 684 | 685 | for (i = 0; i < self->terminated_len; ++i) { 686 | self->x1[4 * i + 0] = r[6 * i + 0]; 687 | self->x1[4 * i + 1] = r[6 * i + 1]; 688 | self->x1[4 * i + 2] = r[6 * i + 2]; 689 | self->x1[4 * i + 3] = r[6 * i + 3]; 690 | self->x2[2 * i + 0] = r[6 * i + 4]; 691 | self->x2[2 * i + 1] = r[6 * i + 5]; 692 | } 693 | } 694 | 695 | static void 696 | ser2par_float_6(ccsds_tc_decoder_t *self, const float *__restrict r) 697 | { 698 | unsigned int i; 699 | 700 | for (i = 0; i < self->terminated_len; ++i) { 701 | self->x1[4 * i + 0] = CCSDS_TC_F2SB(r[6 * i + 0]); 702 | self->x1[4 * i + 1] = CCSDS_TC_F2SB(r[6 * i + 1]); 703 | self->x1[4 * i + 2] = CCSDS_TC_F2SB(r[6 * i + 2]); 704 | self->x1[4 * i + 3] = CCSDS_TC_F2SB(r[6 * i + 3]); 705 | self->x2[2 * i + 0] = CCSDS_TC_F2SB(r[6 * i + 4]); 706 | self->x2[2 * i + 1] = CCSDS_TC_F2SB(r[6 * i + 5]); 707 | } 708 | } 709 | 710 | bool 711 | ccsds_tc_decoder_init( 712 | ccsds_tc_decoder_t *self, 713 | const struct ccsds_tc_decoder_params *params) 714 | { 715 | unsigned int block_len; 716 | unsigned int block_bits; 717 | unsigned int i; 718 | unsigned int terminated_len; 719 | unsigned int n1[] = {0, 2, 2, 3, 0, 4}; 720 | unsigned int n2[] = {0, 1, 1, 1, 0, 2}; 721 | poly_t poly_b = CCSDS_TC_G0; 722 | poly_t poly_f1[4]; 723 | poly_t poly_f2[2]; 724 | 725 | unsigned int rate; 726 | 727 | bool ok = false; 728 | 729 | memset(self, 0, sizeof(ccsds_tc_decoder_t)); 730 | 731 | self->params = *params; 732 | 733 | /* Check block length */ 734 | if (self->params.block_len < CCSDS_TC_BLOCK_LENGTH_223 735 | || self->params.block_len > CCSDS_TC_BLOCK_LENGTH_1115 736 | || self->params.block_len == 3) { 737 | fprintf(stderr, "%s: invalid block length\n", __FUNCTION__); 738 | goto fail; 739 | } 740 | 741 | /* Configure channel rate */ 742 | poly_f1[0] = CCSDS_TC_G0; /* The same for all rates */ 743 | poly_f2[0] = CCSDS_TC_G1; /* Ditto */ 744 | 745 | switch (self->params.rate) { 746 | case 2: 747 | self->ser2par_sb_cb = ser2par_sb_2; 748 | self->ser2par_float_cb = ser2par_float_2; 749 | poly_f1[1] = CCSDS_TC_G1; 750 | break; 751 | 752 | case 3: 753 | self->ser2par_sb_cb = ser2par_sb_3; 754 | self->ser2par_float_cb = ser2par_float_3; 755 | poly_f1[1] = CCSDS_TC_G1; 756 | break; 757 | 758 | case 4: 759 | self->ser2par_sb_cb = ser2par_sb_4; 760 | self->ser2par_float_cb = ser2par_float_4; 761 | poly_f1[1] = CCSDS_TC_G2; 762 | poly_f1[2] = CCSDS_TC_G3; 763 | break; 764 | 765 | case 6: 766 | self->ser2par_sb_cb = ser2par_sb_6; 767 | self->ser2par_float_cb = ser2par_float_6; 768 | poly_f1[1] = CCSDS_TC_G1; 769 | poly_f1[2] = CCSDS_TC_G2; 770 | poly_f1[3] = CCSDS_TC_G3; 771 | poly_f2[1] = CCSDS_TC_G3; 772 | break; 773 | 774 | default: 775 | fprintf(stderr, "%s: invalid rate 1/%d\n", self->params.rate); 776 | goto fail; 777 | } 778 | 779 | rate = self->params.rate; 780 | block_len = self->params.block_len * CCSDS_TC_MINIMUM_BLOCK_LENGTH; 781 | block_bits = block_len << 3; 782 | terminated_len = block_bits + CCSDS_TC_STATE_BITS; 783 | 784 | self->length = block_bits; 785 | self->cw_length = terminated_len * self->params.rate; 786 | self->terminated_len = terminated_len; 787 | 788 | /* Allocate buffers */ 789 | ALLOC(self->permutate_inv, block_bits, int); 790 | ALLOC(self->x1, n1[rate - 1] * terminated_len, softbit_t); 791 | ALLOC(self->x2, n2[rate - 1] * terminated_len, softbit_t); 792 | ALLOC(self->y1, terminated_len, softbit_t); 793 | ALLOC(self->y2, terminated_len, softbit_t); 794 | ALLOC(self->y0, terminated_len, softbit_t); 795 | 796 | memset(self->y2, 0, terminated_len * sizeof(softbit_t)); 797 | memset(self->y0, 0, terminated_len * sizeof(softbit_t)); 798 | 799 | /* Allocate permutator */ 800 | if ((self->permutate_dir = ccsds_tc_decoder_make_permutator(block_len)) == NULL) 801 | goto fail; 802 | 803 | for (i = 0; i < block_bits; ++i) 804 | self->permutate_inv[self->permutate_dir[i]] = i; 805 | 806 | self->n1 = n1[rate - 1]; 807 | 808 | /* Initialize encoders and finish */ 809 | if (!ccsds_tc_bcjr_ctx_init( 810 | &self->dec1, 811 | poly_f1, 812 | poly_b, 813 | n1[rate - 1], 814 | block_bits)) { 815 | fprintf(stderr, "%s: DEC1 initialization failed\n", __FUNCTION__); 816 | goto fail; 817 | } 818 | 819 | if (!ccsds_tc_bcjr_ctx_init( 820 | &self->dec2, 821 | poly_f2, 822 | poly_b, 823 | n2[rate - 1], 824 | block_bits)) { 825 | fprintf(stderr, "%s: DEC2 initialization failed\n", __FUNCTION__); 826 | goto fail; 827 | } 828 | 829 | return true; 830 | 831 | fail: 832 | ccsds_tc_decoder_finalize(self); 833 | 834 | return false; 835 | } 836 | 837 | CCSDS_TC_INLINE void 838 | ccsds_tc_decode(ccsds_tc_decoder_t *self) 839 | { 840 | unsigned int i; 841 | 842 | /* First permutation */ 843 | ccsds_tc_bcjr_ctx_decode( 844 | &self->dec1, 845 | self->x1, 846 | self->y0, 847 | self->y1, 848 | self->params.L_c, 849 | self->permutate_dir); 850 | 851 | ccsds_tc_bcjr_ctx_decode( 852 | &self->dec2, 853 | self->x2, 854 | self->y1, 855 | self->y2, 856 | self->params.L_c, 857 | self->permutate_inv); 858 | 859 | /* Next permutations */ 860 | #pragma GCC unroll 2 861 | for (i = 1; i < self->params.iters; ++i) { 862 | ccsds_tc_bcjr_ctx_decode( 863 | &self->dec1, 864 | self->x1, 865 | self->y2, 866 | self->y1, 867 | self->params.L_c, 868 | self->permutate_dir); 869 | 870 | ccsds_tc_bcjr_ctx_decode( 871 | &self->dec2, 872 | self->x2, 873 | self->y1, 874 | self->y2, 875 | self->params.L_c, 876 | self->permutate_inv); 877 | } 878 | 879 | ccsds_tc_bcjr_invalidate_cache(&self->dec1); 880 | ccsds_tc_bcjr_invalidate_cache(&self->dec2); 881 | } 882 | 883 | void 884 | ccsds_tc_decoder_feed_block(ccsds_tc_decoder_t *self, const softbit_t *block) 885 | { 886 | (self->ser2par_sb_cb) (self, block); 887 | ccsds_tc_decode(self); 888 | } 889 | 890 | void 891 | ccsds_tc_decoder_feed_block_float(ccsds_tc_decoder_t *self, const float *block) 892 | { 893 | (self->ser2par_float_cb) (self, block); 894 | ccsds_tc_decode(self); 895 | } 896 | 897 | void 898 | ccsds_tc_decoder_finalize(ccsds_tc_decoder_t *self) 899 | { 900 | if (self->x1 != NULL) 901 | free(self->x1); 902 | 903 | if (self->x2 != NULL) 904 | free(self->x2); 905 | 906 | if (self->y0 != NULL) 907 | free(self->y0); 908 | 909 | if (self->y1 != NULL) 910 | free(self->y1); 911 | 912 | if (self->y2 != NULL) 913 | free(self->y2); 914 | 915 | if (self->permutate_dir != NULL) 916 | free(self->permutate_dir); 917 | 918 | if (self->permutate_inv != NULL) 919 | free(self->permutate_inv); 920 | 921 | ccsds_tc_bcjr_ctx_finalize(&self->dec1); 922 | ccsds_tc_bcjr_ctx_finalize(&self->dec2); 923 | } 924 | -------------------------------------------------------------------------------- /ccsds-tc.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #ifndef _CCSDS_TC_H 21 | #define _CCSDS_TC_H 22 | 23 | #define _ISOC99_SOURCE 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #define CCSDS_TC_MINIMUM_BLOCK_LENGTH 223 30 | 31 | #ifdef __GNUC__ 32 | # define CCSDS_TC_INLINE static inline __attribute__((always_inline)) 33 | #else 34 | # define CCSDS_TC_INLINE static inline 35 | # define __restrict 36 | #endif 37 | 38 | #ifdef CCSDS_TC_INT_ARITHMETICS 39 | # define CCSDS_TC_SQRT_BITS 6 40 | # define CCSDS_TC_SSQRT (1 << CCSDS_TC_SQRT_BITS) 41 | # define CCSDS_TC_SCALE (1 << (CCSDS_TC_SQRT_BITS << 1)) 42 | # define CCSDS_TC_MAX_LH 0 43 | # define CCSDS_TC_MIN_LH ((softbit_t) 0x80000000) 44 | # define CCSDS_TC_IMPOSSIBLE(x) ((x) == 0x80000000) 45 | # define CCSDS_TC_MAX(a, b) ((a) > (b) ? (a) : (b)) 46 | # define CCSDS_TC_SAFE_SUM(a, b) \ 47 | ((a) == CCSDS_TC_MIN_LH || (b) == CCSDS_TC_MIN_LH \ 48 | ? CCSDS_TC_MIN_LH \ 49 | : (a) + (b)) 50 | 51 | typedef int32_t softbit_t; 52 | #else 53 | # define CCSDS_TC_SCALE 1.0 54 | # define CCSDS_TC_SSQRT 1.0 55 | # define CCSDS_TC_MAX_LH 0 56 | # define CCSDS_TC_MIN_LH -INFINITY 57 | # define CCSDS_TC_IMPOSSIBLE(x) isinf(x) 58 | # define CCSDS_TC_MAX(a, b) fmax(a, b) 59 | # define CCSDS_TC_SAFE_SUM(a, b) ((a) + (b)) 60 | typedef float softbit_t; 61 | #endif 62 | 63 | 64 | #define CCSDS_TC_F2SB(x) ((softbit_t) ((x) * CCSDS_TC_SCALE)) 65 | #define CCSDS_TC_SB2F(x) ((float) (x) / (float) CCSDS_TC_SCALE) 66 | 67 | #define CCSDS_TC_STATE_BITS 4 68 | 69 | #define CCSDS_TC_STATE_NUM (1 << CCSDS_TC_STATE_BITS) 70 | #define CCSDS_TC_STATE_MASK (CCSDS_TC_STATE_NUM - 1) 71 | #define CCSDS_TC_STATE_ZERO 0 72 | #define CCSDS_TC_STATE_UNUSED 0xff 73 | 74 | #define CCSDS_TC_G0 0x19 /* 11001 */ 75 | #define CCSDS_TC_G1 0x1b /* 11011 */ 76 | #define CCSDS_TC_G2 0x15 /* 10101 */ 77 | #define CCSDS_TC_G3 0x1f /* 11111 */ 78 | 79 | #define CCSDS_TC_EBN0_TO_LC(ebn0) (4 * (ebn0)) 80 | 81 | /* Better when state matches word length */ 82 | typedef uint32_t state_t; 83 | typedef state_t poly_t; 84 | 85 | /************************* CONVOLUTIONAL CODE API ***************************/ 86 | 87 | /* 88 | * HOW IS THE CONVOLUTIONAL CODE ACTUALLY REPRESENTED 89 | * 90 | * ccsds_tc_bcjr assumes a generic convolutional code 91 | * over a recursive shift register with the following 92 | * internal wiring: 93 | * 94 | * cn-1 c3 c2 c1 c0 95 | * Dn-1 <- ... <--- D2 <--- D1 <--- D0 <---(+)---- u 96 | * | cn | | | ^ 97 | * v v v v | 98 | * (?)----- ... ----(?)-----(?)-----(?)------/ 99 | * bn b3 b2 b1 100 | * 101 | * In order to recover a systematic output, the output 102 | * of the XOR right before c0 must be XOR'ed back by the 103 | * backward connections again. This is easily achiveable 104 | * if the systematic forward output polynomial matches 105 | * the backward polynomial up to c1. c0 must be set to 1 106 | * in this case. 107 | * 108 | * On the other hand, the j-th bit of the backward polynomial 109 | * refers to the output of the j-1-th latch. b1 is connected to 110 | * the output of D0, b2 is connected to the otput of D1, etc. 111 | * 112 | * For instance, the example provided by Ammar Abh which was based 113 | * on a systematic convolutional code of the following structure: 114 | * 115 | * c0 116 | * D0 <---(+)--- u 117 | * | ^ v0 = c0 ^ c1 118 | * | c1 | v1 = c0 119 | * \-------/ 120 | * 121 | * The forward polynomials for v0 and v1 are: 122 | * G(v0) = 11 123 | * G(v1) = 01 124 | * 125 | * While the backward connection polynomial is simply: 126 | * G(bi) = 1X 127 | * 128 | * The state_t type represents the ltches D0...Dn-1 129 | */ 130 | 131 | struct ccsds_tc_bcjr_ctx { 132 | unsigned int h; /* Information bits */ 133 | unsigned int length; /* Data size as accepted by the algo */ 134 | unsigned int n; /* Codeword length */ 135 | 136 | /* Cached data */ 137 | softbit_t *rv; 138 | bool rv_cached; 139 | 140 | state_t S_last_0; 141 | state_t S_last_1; 142 | 143 | /* Trellis connections */ 144 | poly_t poly_b; 145 | state_t *S_next; 146 | state_t *S_prev; 147 | 148 | /* Codeword table */ 149 | state_t *hv; 150 | 151 | /* Path metrics */ 152 | softbit_t *astar; 153 | softbit_t *bstar; 154 | 155 | /* Branch metric */ 156 | 157 | /* 158 | * gstar_dir[2 * (STATE_NUM * l + Si) + 0]: gamma_l*(Si, next(Si, 0)) 159 | * gstar_dir[2 * (STATE_NUM * l + Si) + 1]: gamma_l*(Si, next(Si, 1)) 160 | */ 161 | softbit_t *gstar_dir; 162 | 163 | /* 164 | * gstar_rev[2 * (STATE_NUM * l + Si) + 0]: gamma_l*(prev(Si, 0), Si) 165 | * gstar_rev[2 * (STATE_NUM * l + Si) + 1]: gamma_l*(prev(Si, 1), Si) 166 | */ 167 | softbit_t *gstar_rev; 168 | softbit_t *min_lh_buf; 169 | }; 170 | 171 | typedef struct ccsds_tc_bcjr_ctx ccsds_tc_bcjr_ctx_t; 172 | 173 | bool ccsds_tc_bcjr_ctx_init( 174 | ccsds_tc_bcjr_ctx_t *self, 175 | const poly_t *poly_f, 176 | poly_t poly_b, 177 | unsigned int n, 178 | unsigned int h); 179 | 180 | bool ccsds_tc_bcjr_ctx_decode( 181 | ccsds_tc_bcjr_ctx_t *self, 182 | const softbit_t *x, 183 | const softbit_t *L_u, 184 | softbit_t *y, 185 | softbit_t L_c, 186 | const int *__restrict permute_out); 187 | 188 | CCSDS_TC_INLINE void 189 | ccsds_tc_bcjr_invalidate_cache(ccsds_tc_bcjr_ctx_t *self) 190 | { 191 | self->rv_cached = false; 192 | } 193 | 194 | void ccsds_tc_bcjr_ctx_finalize(ccsds_tc_bcjr_ctx_t *self); 195 | 196 | /************************* CONVOLUTIONAL CODE API ***************************/ 197 | enum ccsds_tc_decoder_block_length { 198 | CCSDS_TC_BLOCK_LENGTH_223 = 1, 199 | CCSDS_TC_BLOCK_LENGTH_446 = 2, 200 | CCSDS_TC_BLOCK_LENGTH_892 = 4, 201 | CCSDS_TC_BLOCK_LENGTH_1115 = 5 202 | }; 203 | 204 | struct ccsds_tc_decoder_params { 205 | unsigned int iters; 206 | unsigned int rate; 207 | unsigned int block_len; 208 | softbit_t L_c; 209 | }; 210 | 211 | #define ccsds_tc_decoder_params_INITIALIZER \ 212 | { \ 213 | 20, /* iters */ \ 214 | 6, /* rate */ \ 215 | CCSDS_TC_BLOCK_LENGTH_223, /* block_len */ \ 216 | 4, /* L_c */ \ 217 | } 218 | 219 | struct ccsds_tc_decoder { 220 | struct ccsds_tc_decoder_params params; 221 | softbit_t *x1, *x2; 222 | softbit_t *y1, *y2; 223 | softbit_t *y0; 224 | 225 | void (*ser2par_sb_cb) (struct ccsds_tc_decoder *, const softbit_t *); 226 | void (*ser2par_float_cb) (struct ccsds_tc_decoder *, const float *); 227 | 228 | int *permutate_dir; 229 | int *permutate_inv; 230 | unsigned int n1; 231 | unsigned int rate; 232 | unsigned int length; 233 | unsigned int terminated_len; 234 | unsigned int cw_length; 235 | ccsds_tc_bcjr_ctx_t dec1; 236 | ccsds_tc_bcjr_ctx_t dec2; 237 | }; 238 | 239 | typedef struct ccsds_tc_decoder ccsds_tc_decoder_t; 240 | 241 | bool ccsds_tc_decoder_init( 242 | ccsds_tc_decoder_t *self, 243 | const struct ccsds_tc_decoder_params *params); 244 | 245 | CCSDS_TC_INLINE unsigned int 246 | ccsds_tc_decoder_get_block_size(const ccsds_tc_decoder_t *self) 247 | { 248 | return self->length; 249 | } 250 | 251 | CCSDS_TC_INLINE unsigned int 252 | ccsds_tc_decoder_get_codeword_size(const ccsds_tc_decoder_t *self) 253 | { 254 | return self->cw_length; 255 | } 256 | 257 | CCSDS_TC_INLINE float 258 | ccsds_tc_get_error_rate(const ccsds_tc_decoder_t *self) 259 | { 260 | unsigned int i, count = 0; 261 | 262 | for (i = 0; i < self->length; ++i) 263 | count += (self->x1[self->n1 * i] < 0) == (self->y2[i] < 0); 264 | 265 | return (float) count / (float) self->length; 266 | } 267 | 268 | CCSDS_TC_INLINE const softbit_t * 269 | ccsds_tc_get_intermediate_output(const ccsds_tc_decoder_t *self) 270 | { 271 | return self->y1; 272 | } 273 | 274 | CCSDS_TC_INLINE const softbit_t * 275 | ccsds_tc_decoder_get_output(const ccsds_tc_decoder_t *self) 276 | { 277 | return self->y2; 278 | } 279 | 280 | void ccsds_tc_decoder_feed_block(ccsds_tc_decoder_t *self, const softbit_t *block); 281 | void ccsds_tc_decoder_feed_block_float(ccsds_tc_decoder_t *self, const float *block); 282 | 283 | void ccsds_tc_decoder_finalize(ccsds_tc_decoder_t *self); 284 | 285 | #endif /* _CCSDS_TC_H */ 286 | -------------------------------------------------------------------------------- /ccsds-tool.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "ccsds-modem.h" 39 | #include "ccsds-guess.h" 40 | #include "defs.h" 41 | 42 | #define CCSDS_TOOL_VERSION "0.1" 43 | #define CCSDS_TOOL_RATE_REPORT_INTERVAL_S 1 44 | #define CCSDS_TOOL_READ_BUFFER_SIZE (4096 / sizeof(float complex)) 45 | 46 | struct ccsds_tool_options { 47 | const char *guess; 48 | const char *file; 49 | const char *ofile; 50 | unsigned int port; 51 | unsigned int rate_inv; 52 | bool q_channel; 53 | float snr; 54 | unsigned int iters; 55 | unsigned int block_size; 56 | }; 57 | 58 | #define ccsds_tool_options_INITIALIZER \ 59 | { \ 60 | NULL, /* guess */ \ 61 | NULL, /* file */ \ 62 | NULL, /* ofile */ \ 63 | 0, /* port */ \ 64 | 6, /* rate_inv */ \ 65 | true, /* q_channel */ \ 66 | 19.03, /* snr */ \ 67 | 1, /* iters */ \ 68 | 8920, /* block_size */ \ 69 | } 70 | 71 | static int 72 | open_listening_socket(const char *argv0, uint16_t port) 73 | { 74 | struct sockaddr_in server_addr; 75 | struct sockaddr_in client_addr; 76 | unsigned int structsiz; 77 | int sfd, cfd = -1; 78 | int flg; 79 | 80 | flg = 1; 81 | 82 | structsiz = sizeof(struct sockaddr_in); 83 | 84 | server_addr.sin_family = AF_INET; 85 | server_addr.sin_addr.s_addr = INADDR_ANY; 86 | server_addr.sin_port = htons(port); 87 | 88 | if ((sfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { 89 | fprintf( 90 | stderr, 91 | "%s: cannot create socket: %s\n", 92 | argv0, 93 | strerror(errno)); 94 | goto done; 95 | } 96 | 97 | if (setsockopt( 98 | sfd, 99 | SOL_SOCKET, 100 | SO_REUSEADDR, 101 | &flg, 102 | sizeof(int))) { 103 | fprintf( 104 | stderr, 105 | "%s: cannot set SO_REUSEADDR: %s\n", 106 | argv0, 107 | strerror(errno)); 108 | goto done; 109 | } 110 | 111 | if (bind( 112 | sfd, 113 | (struct sockaddr*) &server_addr, 114 | sizeof (struct sockaddr_in))) { 115 | fprintf( 116 | stderr, 117 | "%s: cannot bind socket address: %s\n", 118 | argv0, 119 | strerror(errno)); 120 | goto done; 121 | } 122 | 123 | if (listen(sfd, 5) == -1) { 124 | fprintf( 125 | stderr, 126 | "%s: cannot listen on socket: %s\n", 127 | argv0, 128 | strerror(errno)); 129 | goto done; 130 | } 131 | 132 | signal(SIGPIPE, SIG_IGN); 133 | 134 | cfd = accept( 135 | sfd, 136 | (struct sockaddr *) &client_addr, 137 | &structsiz); 138 | 139 | if (cfd == -1) { 140 | fprintf( 141 | stderr, 142 | "%s: cannot accept connection: %s\n", 143 | argv0, 144 | strerror(errno)); 145 | goto done; 146 | } 147 | 148 | done: 149 | if (sfd != -1) 150 | close(sfd); 151 | 152 | return cfd; 153 | } 154 | 155 | CCSDS_TC_INLINE void 156 | report_rate(unsigned int count, const struct timeval *diff) 157 | { 158 | float rate = (count << 3) / (diff->tv_sec + diff->tv_usec * 1e-6); 159 | 160 | if (rate < 1e3) 161 | fprintf(stderr, "Decode rate: %.2f bps\r", rate); 162 | else if (rate < 1e6) 163 | fprintf(stderr, "Decode rate: %.2f kbps\r", rate * 1e-3); 164 | else 165 | fprintf(stderr, "Decode rate: %.2f Mbps\r", rate * 1e-6); 166 | } 167 | 168 | bool 169 | run_guess(const char *argv0, const struct ccsds_tool_options *opt) 170 | { 171 | struct ccsds_tc_report report; 172 | struct stat sbuf; 173 | unsigned int L; 174 | int fd = -1; 175 | float complex *samples = (float complex *) -1; 176 | bool ok = false; 177 | 178 | if (stat(opt->guess, &sbuf) == -1) { 179 | fprintf( 180 | stderr, 181 | "%s: cannot stat `%s': %s\n", 182 | argv0, 183 | opt->guess, 184 | strerror(errno)); 185 | exit(EXIT_FAILURE); 186 | } 187 | 188 | if ((fd = open(opt->guess, O_RDONLY)) == -1) { 189 | fprintf( 190 | stderr, 191 | "%s: cannot open `%s' for reading: %s\n", 192 | argv0, 193 | opt->guess, 194 | strerror(errno)); 195 | exit(EXIT_FAILURE); 196 | } 197 | 198 | if ((samples = mmap( 199 | NULL, 200 | sbuf.st_size, 201 | PROT_READ, 202 | MAP_PRIVATE, 203 | fd, 204 | 0)) == (const float complex *) -1) { 205 | fprintf( 206 | stderr, 207 | "%s: cannot mmap `%s': %s\n", 208 | argv0, 209 | opt->guess, 210 | strerror(errno)); 211 | exit(EXIT_FAILURE); 212 | } 213 | 214 | L = sbuf.st_size / sizeof(float complex); 215 | 216 | if (!ccsds_tc_report_find_best( 217 | &report, 218 | samples, 219 | L, 220 | pow(10, .1 * opt->snr))) 221 | goto done; 222 | 223 | ok = true; 224 | 225 | done: 226 | if (fd != -1) 227 | close(fd); 228 | 229 | if (samples != (const float complex *) -1) 230 | munmap(samples, sbuf.st_size); 231 | 232 | return ok; 233 | } 234 | 235 | static bool 236 | run_decoder(const char *argv0, const struct ccsds_tool_options *opt) 237 | { 238 | FILE *ifp = stdin; 239 | FILE *ofp = stdout; 240 | float complex *buffer = NULL; 241 | int sfd = -1; 242 | size_t got; 243 | unsigned int count = 0, total = 0; 244 | ccsds_modem_t *modem = NULL; 245 | struct ccsds_modem_params params = ccsds_modem_params_INITIALIZER; 246 | float rate; 247 | struct timeval tv, otv, diff; 248 | bool ok = false; 249 | 250 | params.block_len = opt->block_size / (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3); 251 | params.iters = opt->iters; 252 | params.q_channel = opt->q_channel; 253 | params.sync_snr = powf(10, .1 * opt->snr); 254 | params.rate_inv = opt->rate_inv; 255 | 256 | fprintf(stderr, "CCSDS tool v" CCSDS_TOOL_VERSION " for the Amateur DSN by EA1IYR\n"); 257 | fprintf(stderr, "(c) 2021 Gonzalo J. Carracedo - https://actinid.org\n"); 258 | fprintf(stderr, " Code rate: 1/%d\n", opt->rate_inv); 259 | fprintf(stderr, " Block length: %d bits\n", opt->block_size); 260 | fprintf(stderr, " Turbocode iters: %d\n", params.iters); 261 | fprintf(stderr, " Channel: %c\n", opt->q_channel ? 'Q' : 'I'); 262 | fprintf(stderr, " Sync SNR: %g dB\n", opt->snr); 263 | 264 | TRY_ALLOC(buffer, CCSDS_TOOL_READ_BUFFER_SIZE, float complex); 265 | 266 | if ((modem = ccsds_modem_new(¶ms)) == NULL) 267 | goto done; 268 | 269 | /* Open input file */ 270 | if (opt->port != 0) { 271 | fprintf(stderr, " Waiting for connections to port %u (TCP)\n", opt->port); 272 | if ((sfd = open_listening_socket(argv0, opt->port)) == -1) 273 | goto done; 274 | if ((ifp = fdopen(sfd, "rb")) == NULL) { 275 | fprintf( 276 | stderr, 277 | "%s: failed to open socket as file: %s\n", 278 | argv0, 279 | strerror(errno)); 280 | goto done; 281 | } 282 | } else if (opt->file != NULL) { 283 | if ((ifp = fopen(opt->file, "rb")) == NULL) { 284 | fprintf( 285 | stderr, 286 | "%s: failed to open `%s' for reading: %s\n", 287 | argv0, 288 | strerror(errno)); 289 | goto done; 290 | } 291 | fprintf(stderr, " Input file: %s\n", opt->file); 292 | } else { 293 | fprintf(stderr, " Input file: stdin\n"); 294 | } 295 | 296 | /* Open output file */ 297 | if (opt->ofile) { 298 | if ((ofp = fopen(opt->ofile, "wb")) == NULL) { 299 | fprintf( 300 | stderr, 301 | "%s: failed to open `%s' for writing: %s\n", 302 | argv0, 303 | strerror(errno)); 304 | goto done; 305 | } 306 | fprintf(stderr, " Output file: %s\n", opt->ofile); 307 | } else { 308 | fprintf(stderr, " Output file: stdout\n"); 309 | } 310 | 311 | /* Reader loop */ 312 | gettimeofday(&otv, NULL); 313 | while ((got = 314 | fread( 315 | buffer, 316 | sizeof(float complex), 317 | CCSDS_TOOL_READ_BUFFER_SIZE, 318 | ifp)) > 0) { 319 | if (ccsds_modem_feed(modem, buffer, got)) { 320 | /* Frame found with valid CRC! */ 321 | fwrite( 322 | ccsds_modem_get_frame_data(modem), 323 | ccsds_modem_get_frame_length(modem), 324 | 1, 325 | ofp); 326 | count += ccsds_modem_get_frame_length(modem); 327 | total += ccsds_modem_get_frame_length(modem); 328 | gettimeofday(&tv, NULL); 329 | timersub(&tv, &otv, &diff); 330 | 331 | if (diff.tv_sec >= CCSDS_TOOL_RATE_REPORT_INTERVAL_S) { 332 | report_rate(count, &diff); 333 | otv = tv; 334 | count = 0; 335 | } 336 | } 337 | } 338 | 339 | gettimeofday(&tv, NULL); 340 | timersub(&tv, &otv, &diff); 341 | report_rate(count, &diff); 342 | fputc('\n', stderr); 343 | fprintf(stderr, "%s: %d bytes decoded\n", argv0, total); 344 | 345 | ok = true; 346 | 347 | done: 348 | if (modem != NULL) 349 | ccsds_modem_destroy(modem); 350 | 351 | if (sfd != -1) 352 | close(sfd); 353 | 354 | if (ofp != NULL && ofp != stdout) 355 | fclose(ofp); 356 | 357 | if (ifp != NULL && ifp != stdin) 358 | fclose(ifp); 359 | 360 | if (buffer != NULL) 361 | free(buffer); 362 | 363 | return ok; 364 | } 365 | 366 | static bool 367 | run(const char *argv0, const struct ccsds_tool_options *opt) 368 | { 369 | bool ok; 370 | 371 | if (opt->guess != NULL) 372 | ok = run_guess(argv0, opt); 373 | else 374 | ok = run_decoder(argv0, opt); 375 | 376 | return ok; 377 | } 378 | 379 | void 380 | help(const char *argv0) 381 | { 382 | fprintf(stderr, "Usage:\n"); 383 | fprintf(stderr, " %s [options]\n\n", argv0); 384 | fprintf(stderr, "Attempts to guess and decode CSSDS turbo-coded frames from demodulated\n"); 385 | fprintf(stderr, "32-bit complex float samples.\n\n"); 386 | 387 | fprintf(stderr, "OPTIONS:\n"); 388 | fprintf(stderr, " -g, --guess file Attempt to guess turbocode parameters from input file\n"); 389 | fprintf(stderr, " -r, --rate 1/k Set the code rate to 1/k (default is 1/6)\n"); 390 | fprintf(stderr, " -s, --block-size LEN Set the block size to LEN bits (default is 8920)\n"); 391 | fprintf(stderr, " -S, --sync-snr LEVEL Set the syncword correlation SNR to LEVEL dB\n"); 392 | fprintf(stderr, " (default is 19.03 dB)\n"); 393 | fprintf(stderr, " -c, --component C Sets the complex channel to use (I or Q, default is\n"); 394 | fprintf(stderr, " Q: quadrature channel)\n"); 395 | fprintf(stderr, " -i, --iters NUM Set the number of iterations of the BCJR turbo code\n"); 396 | fprintf(stderr, " decoder (default is 1)\n"); 397 | fprintf(stderr, " -f, --file PATH Read complex samples from a file instead of the\n"); 398 | fprintf(stderr, " standard input\n"); 399 | fprintf(stderr, " -o, --output PATH Write decoded frames to PATH instead of the\n"); 400 | fprintf(stderr, " standard output\n"); 401 | fprintf(stderr, " -l, --listen PORT Read complex samples from a listening socket at port\n"); 402 | fprintf(stderr, " PORT instead of the standard input\n\n"); 403 | fprintf(stderr, " -h, --help This help\n\n"); 404 | 405 | fprintf(stderr, "Copyright (C) 2021 Gonzalo J. Carracedo (EA1IYR)\n"); 406 | fprintf(stderr, "Hints on BCJR optimizations by r00t and Daniel Estévez (EA4GPZ)\n\n"); 407 | fprintf(stderr, "This is free software; see the source for copying conditions. There is NO\n"); 408 | fprintf(stderr, "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"); 409 | } 410 | 411 | static struct option g_options[] = { 412 | {"rate", 1, 0, 'r'}, 413 | {"block-size", 1, 0, 's'}, 414 | {"sync-snr", 1, 0, 'S'}, 415 | {"component", 1, 0, 'c'}, 416 | {"iters", 1, 0, 'i'}, 417 | {"file", 1, 0, 'f'}, 418 | {"output", 1, 0, 'o'}, 419 | {"listen", 1, 0, 'l'}, 420 | {"guess", 1, 0, 'g'}, 421 | {"help", 0, 0, 'h'}, 422 | {0, 0, 0, 0} 423 | }; 424 | 425 | int 426 | main(int argc, char **argv) 427 | { 428 | int c; 429 | int digit_optind = 0; 430 | int option_index; 431 | struct ccsds_tool_options options = ccsds_tool_options_INITIALIZER; 432 | 433 | while ((c = getopt_long( 434 | argc, 435 | argv, 436 | "r:s:S:c:i:g:f:o:l:h", 437 | g_options, 438 | &option_index)) != -1) { 439 | 440 | switch (c) { 441 | case 'r': 442 | if (sscanf(optarg, "1/%u", &options.rate_inv) != 1) { 443 | fprintf(stderr, "%s: invalid rate `%s'\n", argv[0], optarg); 444 | exit(EXIT_FAILURE); 445 | } 446 | break; 447 | 448 | case 's': 449 | if (sscanf(optarg, "%u", &options.block_size) != 1) { 450 | fprintf(stderr, "%s: invalid block size `%s'\n", argv[0], optarg); 451 | exit(EXIT_FAILURE); 452 | } 453 | 454 | if ((options.block_size % (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3)) != 0) { 455 | fprintf( 456 | stderr, 457 | "%s: invalid block size %d: it must be a multiple of %d\n", 458 | argv[0], 459 | options.block_size, 460 | CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3); 461 | exit(EXIT_FAILURE); 462 | } 463 | break; 464 | 465 | case 'S': 466 | if (sscanf(optarg, "%f", &options.snr) != 1) { 467 | fprintf(stderr, "%s: invalid sync SNR `%s'\n", argv[0], optarg); 468 | exit(EXIT_FAILURE); 469 | } 470 | break; 471 | 472 | case 'c': 473 | if (tolower(*optarg) == 'i') { 474 | options.q_channel = false; 475 | } else if (tolower(*optarg) == 'q') { 476 | options.q_channel = true; 477 | } else { 478 | fprintf(stderr, "%s: invalid component `%c'\n", argv[0], *optarg); 479 | exit(EXIT_FAILURE); 480 | } 481 | break; 482 | 483 | case 'g': 484 | if ((options.guess = strdup(optarg)) == NULL) { 485 | fprintf(stderr, "%s: memory exhausted\n", argv[0]); 486 | exit(EXIT_FAILURE); 487 | } 488 | break; 489 | 490 | case 'f': 491 | if ((options.file = strdup(optarg)) == NULL) { 492 | fprintf(stderr, "%s: memory exhausted\n", argv[0]); 493 | exit(EXIT_FAILURE); 494 | } 495 | break; 496 | 497 | case 'o': 498 | if ((options.ofile = strdup(optarg)) == NULL) { 499 | fprintf(stderr, "%s: memory exhausted\n", argv[0]); 500 | exit(EXIT_FAILURE); 501 | } 502 | break; 503 | 504 | case 'l': 505 | if (sscanf(optarg, "%u", &options.port) != 1 506 | || options.port == 0 507 | || options.port > 65536) { 508 | fprintf(stderr, "%s: invalid port `%s'\n", argv[0], optarg); 509 | exit(EXIT_FAILURE); 510 | } 511 | break; 512 | 513 | case 'i': 514 | if (sscanf(optarg, "%u", &options.iters) != 1) { 515 | fprintf(stderr, "%s: invalid iteration number `%s'\n", argv[0], optarg); 516 | exit(EXIT_FAILURE); 517 | } 518 | break; 519 | 520 | case 'h': 521 | help(argv[0]); 522 | exit(EXIT_SUCCESS); 523 | 524 | case '?': 525 | exit(EXIT_FAILURE); 526 | } 527 | } 528 | 529 | if (!run(argv[0], &options)) 530 | exit(EXIT_FAILURE); 531 | 532 | return 0; 533 | } 534 | -------------------------------------------------------------------------------- /correlator.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "correlator.h" 25 | #include "defs.h" 26 | 27 | void 28 | correlator_finalize(correlator_t *self) 29 | { 30 | if (self->sequence != NULL) 31 | free(self->sequence); 32 | } 33 | 34 | bool 35 | correlator_init_from_string(correlator_t *self, const char *bits) 36 | { 37 | unsigned int i; 38 | bool ok = false; 39 | 40 | memset(self, 0, sizeof(correlator_t)); 41 | 42 | self->seq_size = strlen(bits); 43 | 44 | TRY_ALLOC(self->sequence, self->seq_size, float); 45 | 46 | for (i = 0; i < self->seq_size; ++i) { 47 | if (bits[i] == '0') 48 | self->sequence[i] = -1; 49 | else if (bits[i] == '1') 50 | self->sequence[i] = +1; 51 | else 52 | self->sequence[i] = 0; 53 | } 54 | 55 | ok = true; 56 | 57 | done: 58 | if (!ok) 59 | correlator_finalize(self); 60 | 61 | return ok; 62 | } 63 | 64 | bool 65 | correlator_find(correlator_t *self, const float *__restrict x, unsigned int len, float SNR) 66 | { 67 | unsigned int i, j, p; 68 | float threshold = 0; 69 | float dotprod = 0; 70 | bool found = false; 71 | 72 | threshold = 0; 73 | 74 | if (len < self->seq_size) 75 | return false; 76 | 77 | for (i = 0; i < len; ++i) 78 | threshold += x[i] * x[i]; 79 | 80 | /* 81 | * This is sigma2 as seen at the correlators output in the absence 82 | * of correlation. 83 | */ 84 | threshold *= (float) self->seq_size / (float) len; 85 | threshold *= SNR; 86 | 87 | self->snr = 0; 88 | 89 | for (i = 0; i < len - self->seq_size; ++i) { 90 | dotprod = 0; 91 | p = i; 92 | for (j = 0; j < self->seq_size; ++j) 93 | dotprod += self->sequence[j] * x[p++]; 94 | 95 | if (dotprod > threshold && fabs(dotprod / threshold) > self->snr) { 96 | self->snr = fabs(dotprod / threshold); 97 | self->reverse = dotprod < 0; 98 | self->where = i; 99 | found = true; 100 | } 101 | } 102 | 103 | return found; 104 | } 105 | 106 | bool 107 | correlator_find_complex( 108 | correlator_t *self, 109 | const float complex *__restrict x, 110 | unsigned int len, 111 | float SNR, 112 | bool quadrature) 113 | { 114 | unsigned int i, j, p; 115 | float threshold = 0; 116 | float sigma2 = 0; 117 | float dotprod = 0; 118 | float corrpwr; 119 | bool found = false; 120 | 121 | sigma2 = 0; 122 | 123 | if (len < self->seq_size) 124 | return false; 125 | 126 | if (quadrature) 127 | for (i = 0; i < len; ++i) 128 | sigma2 += cimagf(x[i]) * cimagf(x[i]); 129 | else 130 | for (i = 0; i < len; ++i) 131 | sigma2 += crealf(x[i]) * crealf(x[i]); 132 | 133 | /* 134 | * This is sigma2 as seen at the correlators output in the absence 135 | * of correlation. 136 | */ 137 | sigma2 *= (float) self->seq_size / (float) len; 138 | threshold = SNR * sigma2; 139 | 140 | self->snr = 0; 141 | 142 | for (i = 0; i < len - self->seq_size; ++i) { 143 | dotprod = 0; 144 | p = i; 145 | 146 | if (quadrature) 147 | for (j = 0; j < self->seq_size; ++j) 148 | dotprod += self->sequence[j] * cimagf(x[p++]); 149 | else 150 | for (j = 0; j < self->seq_size; ++j) 151 | dotprod += self->sequence[j] * crealf(x[p++]); 152 | 153 | /* 154 | * The idea is that the mean of the square of the correlation 155 | * is given by len(seq_size) * Var(x) 156 | */ 157 | corrpwr = dotprod * dotprod; 158 | 159 | if (corrpwr > threshold && corrpwr / sigma2 > self->snr) { 160 | self->snr = corrpwr / sigma2; 161 | self->reverse = dotprod < 0; 162 | self->where = i; 163 | found = true; 164 | } 165 | } 166 | 167 | return found; 168 | } 169 | 170 | -------------------------------------------------------------------------------- /correlator.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #ifndef _CCSDS_CORRELATOR_H 21 | #define _CCSDS_CORRELATOR_H 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | struct correlator { 29 | unsigned int seq_size; 30 | float *sequence; 31 | 32 | /* To be updated when a candidate is found */ 33 | unsigned int where; 34 | float snr; 35 | bool reverse; 36 | }; 37 | 38 | typedef struct correlator correlator_t; 39 | 40 | #define correlator_INITIALIZER {0, NULL, 0, 0, 0} 41 | 42 | bool correlator_init_from_string(correlator_t *self, const char *bits); 43 | 44 | bool correlator_find_complex( 45 | correlator_t *self, 46 | const float complex *x, 47 | unsigned int len, 48 | float SNR, 49 | bool quadrature); 50 | 51 | bool correlator_find( 52 | correlator_t *self, 53 | const float *x, 54 | unsigned int len, 55 | float SNR); 56 | 57 | 58 | void correlator_finalize(correlator_t *self); 59 | 60 | static inline float 61 | correlator_get_snr(const correlator_t *self) 62 | { 63 | return self->snr; 64 | } 65 | 66 | static inline unsigned int 67 | correlator_get_pos(const correlator_t *self) 68 | { 69 | return self->where; 70 | } 71 | 72 | static inline bool 73 | correlator_is_reverse(const correlator_t *self) 74 | { 75 | return self->reverse; 76 | } 77 | 78 | #endif /* _CCSDS_CORRELATOR_H */ 79 | -------------------------------------------------------------------------------- /defs.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEFS_H 2 | #define _DEFS_H 3 | 4 | #include 5 | #include 6 | 7 | #define TRY_ALLOC(where, N, what) \ 8 | if ((where = malloc(N * sizeof(what))) == NULL) { \ 9 | fprintf( \ 10 | stderr, \ 11 | "%s: failed to allocate %d elements of type %s\n", \ 12 | __FILE__, \ 13 | N, \ 14 | #what); \ 15 | goto done; \ 16 | } 17 | 18 | #define TRY_CALLOC(where, N, what) \ 19 | if ((where = calloc(N, sizeof(what))) == NULL) { \ 20 | fprintf( \ 21 | stderr, \ 22 | "%s: failed to allocate %d elements of type %s\n", \ 23 | __FILE__, \ 24 | N, \ 25 | #what); \ 26 | goto done; \ 27 | } 28 | 29 | #ifndef __GNUC__ 30 | # ifdef __restricted 31 | # undef __restricted 32 | # endif /* __restricted */ 33 | # define __restricted 34 | #endif /* __GNUC__ */ 35 | 36 | #endif /* _DEFS_H */ 37 | -------------------------------------------------------------------------------- /lfsr.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "lfsr.h" 26 | 27 | #define TRY(expr) if (!(expr)) goto fail 28 | 29 | #define STRBUILD_BSIZ 16 30 | 31 | static inline unsigned int 32 | popcount64(uint64_t b) 33 | { 34 | b = (b & 0x5555555555555555ull) + (b >> 1 & 0x5555555555555555ull); 35 | b = (b & 0x3333333333333333ull) + (b >> 2 & 0x3333333333333333ull); 36 | b = b + (b >> 4) & 0x0F0F0F0F0F0F0F0Full; 37 | b = b + (b >> 8); 38 | b = b + (b >> 16); 39 | b = b + (b >> 32) & 0x0000007F; 40 | 41 | return (unsigned int) b; 42 | } 43 | 44 | int 45 | is_asciiz(const char *buf, int lbound, int ubound) 46 | { 47 | register int i; 48 | 49 | for (i = lbound; i < ubound; i++) 50 | if (!buf[i]) 51 | return i + 1; 52 | return 0; 53 | } 54 | 55 | char * 56 | vstrbuild(const char *fmt, va_list ap) 57 | { 58 | char *out; 59 | int size, zeroindex; 60 | int last; 61 | va_list copy; 62 | 63 | last = 0; 64 | 65 | if (fmt != NULL) { 66 | if (!*fmt) { 67 | out = malloc(1); 68 | out[0] = '\0'; 69 | return out; 70 | } 71 | 72 | va_copy(copy, ap); 73 | size = vsnprintf(NULL, 0, fmt, copy) + 1; 74 | va_end(copy); 75 | 76 | if ((out = malloc(size)) == NULL) 77 | return NULL; 78 | 79 | va_copy(copy, ap); 80 | vsnprintf(out, size, fmt, copy); 81 | va_end(copy); 82 | 83 | for (;;) { 84 | if ((zeroindex = is_asciiz(out, last, size)) != 0) 85 | break; 86 | 87 | last = size; 88 | size += STRBUILD_BSIZ; 89 | 90 | out = realloc(out, size); /* Reasignamos */ 91 | 92 | va_copy (copy, ap); 93 | vsnprintf(out, size, fmt, copy); 94 | va_end (copy); 95 | } 96 | } 97 | else 98 | out = NULL; 99 | 100 | return out; 101 | } 102 | 103 | 104 | /* Construye una cadena mediante el formato printf y devuelve un 105 | puntero a la cadena resultado. DEBES liberar tu mismo la salida. */ 106 | 107 | /* FIXME: Buscar alguna alternativa mas portable */ 108 | char* 109 | strbuild (const char *fmt, ...) 110 | { 111 | char *out; 112 | va_list ap; 113 | 114 | va_start (ap, fmt); 115 | out = vstrbuild (fmt, ap); 116 | va_end (ap); 117 | 118 | return out; 119 | } 120 | 121 | void 122 | lfsr_destroy(lfsr_t *self) 123 | { 124 | free(self); 125 | } 126 | 127 | char * 128 | lfsr_get_poly(const lfsr_t *self) 129 | { 130 | unsigned int i; 131 | char *prev = NULL; 132 | char *poly = NULL; 133 | 134 | for (i = 63; i >= 1; --i) 135 | if ((self->mask & (1ull << i)) != 0) { 136 | TRY(poly = strbuild("%sx^%d + ", prev == NULL ? "" : prev, i)); 137 | if (prev != NULL) 138 | free(prev); 139 | prev = poly; 140 | } 141 | 142 | TRY(poly = strbuild("%s1", prev == NULL ? "" : prev)); 143 | 144 | if (prev != NULL) 145 | free(prev); 146 | 147 | return poly; 148 | 149 | fail: 150 | if (prev != NULL) 151 | free(prev); 152 | 153 | return NULL; 154 | } 155 | 156 | void 157 | lfsr_reset(lfsr_t *self) 158 | { 159 | self->reg = (1ull << (2 * self->len)) - 1; 160 | } 161 | 162 | lfsr_t * 163 | lfsr_new(const unsigned int *taps, unsigned int tap_len) 164 | { 165 | lfsr_t *self = NULL; 166 | unsigned int i; 167 | 168 | if ((self = calloc(1, sizeof(lfsr_t))) == NULL) 169 | goto fail; 170 | 171 | for (i = 0; i < tap_len; ++i) { 172 | if (taps[i] >= LFSR_MAX_TAPS) { 173 | fprintf(stderr, "Invalid tap %d\n", taps[i]); 174 | goto fail; 175 | } 176 | 177 | self->mask |= 1ull << taps[i]; 178 | if (self->len < taps[i]) 179 | self->len = taps[i]; 180 | } 181 | 182 | lfsr_reset(self); 183 | 184 | self->cycle_len = (1ull << self->len) - 1; 185 | 186 | --self->len; 187 | 188 | return self; 189 | 190 | fail: 191 | if (self != NULL) 192 | lfsr_destroy(self); 193 | 194 | return NULL; 195 | } 196 | 197 | static inline uint8_t 198 | lfsr_core(lfsr_t *self, uint8_t input, unsigned int direction) 199 | { 200 | uint8_t x = input & 1; 201 | uint8_t y = (popcount64(self->reg & self->mask) & 1) ^ x; 202 | uint8_t newbit = direction ? x : y; 203 | uint8_t output = direction ? y : self->reg & 1; 204 | 205 | self->reg = (self->reg >> 1) | (newbit << self->len); 206 | 207 | return y; 208 | } 209 | 210 | uint8_t 211 | lfsr_scramble(lfsr_t *self, uint8_t input) 212 | { 213 | return lfsr_core(self, input, 0); 214 | } 215 | 216 | uint8_t 217 | lfsr_descramble(lfsr_t *self, uint8_t input) 218 | { 219 | return lfsr_core(self, input, 1); 220 | } 221 | 222 | -------------------------------------------------------------------------------- /lfsr.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #ifndef _LFSR_H 21 | #define _LFSR_H 22 | 23 | #include 24 | 25 | #define LFSR_MAX_TAPS 63 26 | 27 | struct lfsr { 28 | uint64_t mask; 29 | uint64_t reg; 30 | uint64_t len; 31 | uint64_t cycle_len; /* Assuming it's primitive */ 32 | }; 33 | 34 | typedef struct lfsr lfsr_t; 35 | 36 | static inline uint64_t 37 | lfsr_get_cycle_len(const lfsr_t *self) 38 | { 39 | return self->cycle_len; 40 | } 41 | 42 | lfsr_t *lfsr_new(const unsigned int *taps, unsigned int tap_len); 43 | uint8_t lfsr_scramble(lfsr_t *self, uint8_t input); 44 | uint8_t lfsr_descramble(lfsr_t *self, uint8_t input); 45 | void lfsr_reset(lfsr_t *self); 46 | char *lfsr_get_poly(const lfsr_t *self); 47 | void lfsr_destroy(lfsr_t *); 48 | 49 | #endif /* _LFSR_H */ 50 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, version 3. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this program. If not, see 16 | 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "ccsds-tc.h" 27 | 28 | #define CCSDS_TC_TEST_ITERS 3000 29 | 30 | enum ccsds_tc_test_type { 31 | CCSDS_TC_TEST_TYPE_SINGLE_RUN, 32 | CCSDS_TC_TEST_TYPE_DECODER_RATE, 33 | CCSDS_TC_TEST_TYPE_ITERATION_RATE 34 | }; 35 | 36 | void 37 | run_benchmark(const softbit_t *u, const float *r, enum ccsds_tc_test_type type) 38 | { 39 | ccsds_tc_decoder_t decoder; 40 | const softbit_t *y; 41 | float rate; 42 | struct ccsds_tc_decoder_params params = ccsds_tc_decoder_params_INITIALIZER; 43 | struct timeval tv, otv, diff; 44 | unsigned int i; 45 | unsigned int length; 46 | bool b1, b2, b3; 47 | 48 | params.block_len = CCSDS_TC_BLOCK_LENGTH_223; 49 | params.iters = type == CCSDS_TC_TEST_TYPE_ITERATION_RATE 50 | ? CCSDS_TC_TEST_ITERS 51 | : 1; 52 | 53 | params.L_c = CCSDS_TC_EBN0_TO_LC(CCSDS_TC_F2SB(powf(10, .1 * -2))); 54 | params.rate = 6; 55 | 56 | if (!ccsds_tc_decoder_init(&decoder, ¶ms)) { 57 | fprintf(stderr, "%s: failed to create decoder\n", __FUNCTION__); 58 | goto done; 59 | } 60 | 61 | length = ccsds_tc_decoder_get_block_size(&decoder); 62 | 63 | switch (type) { 64 | case CCSDS_TC_TEST_TYPE_SINGLE_RUN: 65 | printf("Running one-shot decoding with %d iters\n", params.iters); 66 | ccsds_tc_decoder_feed_block_float(&decoder, r); 67 | y = ccsds_tc_decoder_get_output(&decoder); 68 | 69 | for (i = 0; i < length; ++i) { 70 | b1 = u[i] > 0; 71 | b2 = r[i * 6] > 0; 72 | b3 = y[i] > 0; 73 | 74 | if (b1 != b2 || b2 != b3) { 75 | printf("BIT %5d / %5d CORRUPTED: ", i + 1, length); 76 | if (b1 == b3) { 77 | printf("\033[1;32mCORRECTED\033[0m\n"); 78 | } else { 79 | printf("\033[1;31mFAILED (L(u) = %g)\033[0m\n", CCSDS_TC_SB2F(y[i])); 80 | } 81 | } 82 | } 83 | 84 | break; 85 | 86 | case CCSDS_TC_TEST_TYPE_DECODER_RATE: 87 | printf( 88 | "Decoding the same %d-bit block %d times, press Ctrl+C to abort\n", 89 | ccsds_tc_decoder_get_block_size(&decoder), 90 | CCSDS_TC_TEST_ITERS); 91 | #ifdef CCSDS_TC_INT_ARITHMETICS 92 | printf("Note: integer arithmetics enabled. Expect overhead due to float-to-softbit\n"); 93 | printf("conversions prior to each decoding.\n"); 94 | #endif /* CCSDS_TC_INT_ARITHMETICS */ 95 | 96 | for (;;) { 97 | gettimeofday(&otv, NULL); 98 | for (i = 0; i < CCSDS_TC_TEST_ITERS; ++i) 99 | ccsds_tc_decoder_feed_block_float(&decoder, r); 100 | gettimeofday(&tv, NULL); 101 | 102 | gettimeofday(&tv, NULL); 103 | timersub(&tv, &otv, &diff); 104 | 105 | rate = (CCSDS_TC_TEST_ITERS * length) / (diff.tv_sec + 1e-6 * diff.tv_usec); 106 | 107 | printf("Output rate: "); 108 | if (rate < 1e3) 109 | printf("%.3f bps\n", rate); 110 | else if (rate < 1e6) 111 | printf("%.3f kbps\n", rate * 1e-3); 112 | else 113 | printf("%.3f Mbps\n", rate * 1e-6); 114 | } 115 | break; 116 | 117 | case CCSDS_TC_TEST_TYPE_ITERATION_RATE: 118 | printf( 119 | "Performing %d iterations over the same %d-bit block, press Ctrl+C to abort\n", 120 | CCSDS_TC_TEST_ITERS, 121 | ccsds_tc_decoder_get_block_size(&decoder)); 122 | 123 | for (;;) { 124 | gettimeofday(&otv, NULL); 125 | ccsds_tc_decoder_feed_block_float(&decoder, r); 126 | gettimeofday(&tv, NULL); 127 | 128 | gettimeofday(&tv, NULL); 129 | timersub(&tv, &otv, &diff); 130 | 131 | rate = (CCSDS_TC_TEST_ITERS * length) / (diff.tv_sec + 1e-6 * diff.tv_usec); 132 | 133 | printf("Iteration rate: "); 134 | if (rate < 1e3) 135 | printf("%.3f bps\n", rate); 136 | else if (rate < 1e6) 137 | printf("%.3f kbps\n", rate * 1e-3); 138 | else 139 | printf("%.3f Mbps\n", rate * 1e-6); 140 | } 141 | break; 142 | } 143 | 144 | done: 145 | ccsds_tc_decoder_finalize(&decoder); 146 | } 147 | 148 | int 149 | main(int argc, char **argv) 150 | { 151 | size_t size; 152 | enum ccsds_tc_test_type type = CCSDS_TC_TEST_TYPE_SINGLE_RUN; 153 | void *map, *map2; 154 | FILE *fp = fopen("turbosignal.raw", "rb"); 155 | FILE *fp2 = fopen("rightsignal.raw", "rb"); 156 | 157 | if (fp == NULL) { 158 | perror("turbosignal.raw"); 159 | return 1; 160 | } 161 | 162 | if (fp2 == NULL) { 163 | perror("rightsignal.raw"); 164 | return 1; 165 | } 166 | 167 | fseek(fp, 0, SEEK_END); 168 | size = ftell(fp); 169 | fseek(fp, 0, SEEK_SET); 170 | 171 | size /= sizeof(float); 172 | 173 | map = mmap(NULL, size * sizeof(float), PROT_READ, MAP_PRIVATE, fileno(fp), 0); 174 | if (map == (void *) - 1) { 175 | perror("mmap turbosignal"); 176 | exit(EXIT_FAILURE); 177 | } 178 | 179 | map2 = mmap(NULL, size * sizeof(float) / 6, PROT_READ, MAP_PRIVATE, fileno(fp2), 0); 180 | if (map == (void *) - 1) { 181 | perror("mmap rightsignal"); 182 | exit(EXIT_FAILURE); 183 | } 184 | fclose(fp); 185 | 186 | if (argc < 2) { 187 | fprintf(stderr, "%s: no benchmark specified, defaulting to single\n", argv[0]); 188 | } else { 189 | if (strcmp(argv[1], "single") == 0) 190 | type = CCSDS_TC_TEST_TYPE_SINGLE_RUN; 191 | else if (strcmp(argv[1], "rate") == 0) 192 | type = CCSDS_TC_TEST_TYPE_DECODER_RATE; 193 | else if (strcmp(argv[1], "iter") == 0) 194 | type = CCSDS_TC_TEST_TYPE_ITERATION_RATE; 195 | else { 196 | fprintf(stderr, "%s: invalid benchmark type `%s'\n", argv[0], argv[1]); 197 | exit(EXIT_FAILURE); 198 | } 199 | } 200 | 201 | run_benchmark(map2, map, type); 202 | 203 | return 0; 204 | } 205 | --------------------------------------------------------------------------------