File: mobitex_fec.py

package info (click to toggle)
gr-satellites 5.8.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,836 kB
  • sloc: python: 29,546; cpp: 5,448; ansic: 1,247; sh: 118; makefile: 24
file content (185 lines) | stat: -rwxr-xr-x 5,133 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright 2025 Fabian P. Schmidt <kerel@mailbox.org>
#
# This file is part of gr-satellites
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Implementation of the Mobitex FEC

This module implements the FEC algorithm used in the Mobitex protocol
for error detection and correction.

It is a (12,8,3) linear code, thus it can encode 8-bit data with 4-bit
FEC codes and detect/correct single-bit errors in the combined 12-bit words.

- Reliable two-bit error correction is not possible with this FEC scheme.
- Many collisions occur where a two-bit error produces the same syndrome as
  a single-bit error.

References:
[1]: ttps://git.tu-berlin.de/rft/com/mobitub-2/-/blob/master/gr-tnc_nx/
[2]: MIT OpenCourseWare 6.02
     Introduction to EECS II: Digital Communication Systems
     Chapter 5: Coping with Bit Errors using Error Correction Codes
     https://ocw.mit.edu/courses/
[3]: https://destevez.net/2016/09/some-notes-on-beesat-and-mobitex-nx/
"""
import sys

from enum import IntEnum
from typing import Tuple


# Matrix H, in 8 and 12 column variant
FEC_MATRIX_8b = [
    0b11101100,
    0b11010011,
    0b10111010,
    0b01110101,
]
FEC_MATRIX_12b = [
    0b11101100_1000,
    0b11010011_0100,
    0b10111010_0010,
    0b01110101_0001,
]


class Status(IntEnum):
    NO_ERROR = 0
    ERROR_CORRECTED = 1
    ERROR_UNCORRECTABLE = 2


if sys.version_info >= (3, 10):
    def calculate_even_parity(value: int) -> int:
        return value.bit_count() & 1
else:
    def calculate_even_parity(value: int) -> int:
        """Calculate even parity of a value, using XOR folding.

        value must be 16-bit or smaller.

        Returns:
            Integer (0 or 1) representing (even or odd) parity
        """
        value ^= (value >> 16)
        value ^= (value >> 8)
        value ^= (value >> 4)
        value ^= (value >> 2)
        value ^= (value >> 1)

        return value & 1


def init_syndrome_table() -> dict:
    """Initialize the syndrome lookup table.

    Returns:
        Dict mapping syndromes to error positions
    """
    table = {}

    # Process each possible single-bit error position
    for pos in range(12):  # 12 bits total (8 data + 4 FEC)
        # Create word with error at position pos
        word = 1 << pos

        # Calculate syndrome for this error
        syndrome = 0
        for i, mask in enumerate(FEC_MATRIX_12b):
            masked = word & mask
            parity = calculate_even_parity(masked)
            syndrome |= (parity << (3 - i))

        table[syndrome] = pos
    return table


SYNDROME_TABLE = init_syndrome_table()


def encode(message: int) -> int:
    """
    Takes 8-bit byte, returns 12-bit codeword.
    """
    fec = 0
    # Generate each FEC bit and place in correct position
    for i, mask in enumerate(FEC_MATRIX_8b):
        masked = message & mask
        parity = calculate_even_parity(masked)

        # Place parity bit in correct position (3-0)
        fec |= (parity << (3 - i))

    # Combine data byte and FEC into 12-bit codeword
    codeword = (message << 4) | fec

    return codeword


def decode(codeword: int):
    """
    Takes 12-bit codeword, returns tuple of (message, corrected_fec, status).

    output:
        corrected_message (int): 1 control byte, error corrected
        corrected_fec (int)    : 4 parity bits, error corrected
        status (Status)        : (0 - NO ERROR,
                                  1 - ERROR CORRECTED,
                                  2 - ERROR UNCORRECTABLE)
    """
    if not 0 <= codeword <= 0xFFF:
        raise ValueError("Input must be a 12-bit value (0-4095)")

    syndrome = 0
    for i, row in enumerate(FEC_MATRIX_12b):
        masked = codeword & row
        parity = calculate_even_parity(masked)

        # Build syndrome from right to left (like FEC bits)
        syndrome |= (parity << (3 - i))

    if syndrome == 0:
        message = codeword >> 4
        fec = codeword & 0x0F
        return message, fec, Status.NO_ERROR

    # Look up error position in syndrome table
    try:
        error_position = SYNDROME_TABLE[syndrome]
    except KeyError:
        message = codeword >> 4
        fec = codeword & 0x0F
        return message, fec, Status.ERROR_UNCORRECTABLE

    # Correct the error by flipping the bit
    corrected = codeword ^ (1 << error_position)

    message = corrected >> 4
    fec = corrected & 0x0F
    return message, fec, Status.ERROR_CORRECTED


def split(word: int) -> Tuple[int, int]:
    """Split 12-bit codeword into data byte and FEC."""
    return (word >> 4), (word & 0xF)


def pack_2b(byte0: int, byte1: int) -> bytes:
    """Pack two 12-bit codewords into three bytes."""
    encoded_byte1 = byte0 >> 4
    encoded_byte2 = ((byte0 & 0x0F) << 4) | (byte1 >> 8)
    encoded_byte3 = byte1 & 0xFF
    return bytes([encoded_byte1, encoded_byte2, encoded_byte3])


def unpack_2b(code: bytes) -> Tuple[int, int]:
    """From three bytes unpack two 12-bit codewords."""
    codeword0 = (code[0] << 4) | (code[1] >> 4)
    codeword1 = ((code[1] & 0x0F) << 8) | code[2]
    return codeword0, codeword1