NMT: Implement Hagelbarger Code

This will correct burst errors of received messages. If the message
is too corrupted, it will be ignored, because some element may not
match then. The digits and line signals are checked for consistency,
since they are repeated serveral times in a message.
This commit is contained in:
Andreas Eversberg 2017-07-15 21:49:28 +02:00
parent cd9cb9a107
commit 0c9de251be
8 changed files with 239 additions and 160 deletions

1
.gitignore vendored
View File

@ -35,3 +35,4 @@ src/test/test_emphasis
src/test/test_dms
src/test/test_sms
src/test/test_performance
src/test/test_hagelbarger

View File

@ -8,6 +8,7 @@ nmt_SOURCES = \
countries.c \
transaction.c \
dsp.c \
hagelbarger.c \
frame.c \
dms.c \
sms.c \

View File

@ -27,6 +27,7 @@
#include "../common/timer.h"
#include "nmt.h"
#include "frame.h"
#include "hagelbarger.h"
uint64_t nmt_encode_channel(int channel, int power)
{
@ -969,161 +970,25 @@ static void assemble_frame(frame_t *frame, uint8_t *digits, int debug)
}
}
/* encode digits of a frame to 166 bits */
static void encode_digits(const uint8_t *digits, char *bits)
{
uint8_t x[64];
int i;
uint8_t digit;
/* copy bit sync and frame sync */
memcpy(bits, "10101010101010111100010010", 26);
bits += 26;
for (i = 0; i < 16; i++) {
digit = *digits++;
x[(i << 2) + 0] = (digit >> 3) & 1;
x[(i << 2) + 1] = (digit >> 2) & 1;
x[(i << 2) + 2] = (digit >> 1) & 1;
x[(i << 2) + 3] = digit & 1;
}
/* parity check bits */
for (i = 0; i < 3; i++)
bits[(i << 1)] = '1' - x[i];
for (i = 3; i < 64; i++)
bits[(i << 1)] = '1' - (x[i] ^ x[i - 3]);
for (i = 64; i < 67; i++)
bits[(i << 1)] = '1' - x[i - 3];
for (i = 67; i < 70; i++)
bits[(i << 1)] = '1';
/* information bits */
for (i = 0; i < 6; i++)
bits[(i << 1) + 1] = '0';
for (i = 6; i < 70; i++)
bits[(i << 1) + 1] = x[i - 6] + '0';
}
/* debug parity check */
//#define DEBUG_DECODE
/* decode digits from 140 bits (not including sync) */
// FIXME: do real convolutional decoding
static int decode_digits(uint8_t *digits, const char *bits, int callack)
{
uint8_t x[64];
int i, short_frame = 0;
/* information bits */
for (i = 0; i < 6; i++) {
if (bits[(i << 1) + 1] != '0') {
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad at information bit #%d.\n", i);
#endif
return -1;
}
}
for (i = 6; i < 70; i++)
x[i - 6] = bits[(i << 1) + 1] - '0';
/* create digits */
for (i = 0; i < 16; i++) {
digits[i] = ((x[(i << 2) + 0] & 1) << 3)
| ((x[(i << 2) + 1] & 1) << 2)
| ((x[(i << 2) + 2] & 1) << 1)
| (x[(i << 2) + 3] & 1);
}
/* check for short frame */
if (callack && (digits[3] == 1 || digits[3] == 15)) {
digits[13] = 0;
digits[14] = 0;
digits[15] = 0;
short_frame = 1;
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Received shortened frame\n");
#endif
}
/* parity check bits */
for (i = 0; i < 3; i++) {
if (bits[(i << 1)] != '1' - x[i]) {
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad at parity bit #%d.\n", i);
#endif
return -1;
}
}
#if 0
#warning this check does not work, since we get error even at bit 50
for (i = 3; i < ((short_frame) ? 52 : 64); i++) {
if (bits[(i << 1)] != '1' - (x[i] ^ x[i - 3])) {
/* According to NMT Doc 450-3, bits after Y(114) shall
* be omitted for short frame. It would make more sense
* to stop after Y(116), so only the last three digits
* are omitted and not the last bit of digit13 also.
* Tests have showed that it we receive correctly up to
* Y(116), but we ignore an error, if only up to Y(114)
* is received.
*/
if (short_frame && i == 51) {
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad after bit Y(114), ignoring.\n");
digits[13] = 0;
break;
}
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad at parity bit #%d.\n", i);
#endif
return -1;
}
}
#endif
/* Tests showed corrupt frame at parity #50 (i == 50)
* We just ignore whatever we receive after 48 bits of checksum.
* This is not the correct approach, but in case of a corrupt
* frame 10.a, we would drop it, if it failes later checks.
*/
for (i = 3; i < ((short_frame) ? 48 : 64); i++) {
if (bits[(i << 1)] != '1' - (x[i] ^ x[i - 3])) {
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad at parity bit #%d.\n", i);
#endif
return -1;
}
}
if (short_frame)
return 0;
for (i = 64; i < 67; i++) {
if (bits[(i << 1)] != '1' - x[i - 3]) {
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad at parity bit #%d.\n", i);
#endif
return -1;
}
}
for (i = 67; i < 70; i++) {
if (bits[(i << 1)] != '1') {
#ifdef DEBUG_DECODE
PDEBUG(DFRAME, DEBUG_DEBUG, "Frame bad at parity bit #%d.\n", i);
#endif
return -1;
}
}
return 0;
}
/* encode frame to bits
* debug can be turned on or off
*/
const char *encode_frame(frame_t *frame, int debug)
{
uint8_t digits[16];
uint8_t digits[16], message[9], code[18];
static char bits[166];
int i;
assemble_frame(frame, digits, debug);
encode_digits(digits, bits);
/* hagelbarger code */
message[8] = 0x00;
for (i = 0; i < 8; i++)
message[i] = (digits[i * 2] << 4) | digits[i * 2 + 1];
hagelbarger_encode(message, code, 72);
memcpy(bits, "10101010101010111100010010", 26);
for (i = 0; i < 140; i++)
bits[i + 26] = ((code[i / 8] >> (7 - (i & 7))) & 1) + '0';
return bits;
}
@ -1131,12 +996,19 @@ const char *encode_frame(frame_t *frame, int debug)
/* decode bits to frame */
int decode_frame(frame_t *frame, const char *bits, enum nmt_direction direction, int callack)
{
uint8_t digits[16];
int rc;
uint8_t digits[16], message[8], code[19];
int i;
/* hagelbarger code */
memset(code, 0x00, sizeof(code));
for (i = 0; i < 140; i++)
code[i / 8] |= (bits[i] & 1) << (7 - (i & 7));
hagelbarger_decode(code, message, 64);
for (i = 0; i < 8; i++) {
digits[i * 2] = message[i] >> 4;
digits[i * 2 + 1] = message[i] & 0x0f;
}
rc = decode_digits(digits, bits, callack);
if (rc < 0)
return rc;
disassemble_frame(frame, digits, direction, callack);
return 0;

87
src/nmt/hagelbarger.c Normal file
View File

@ -0,0 +1,87 @@
/* Hagelbarger (6,19) code
*
* A burst up to 6 encoded bits may be corrupt, to correct them.
* After corrupt bits, a minimum of 19 bits must be correct to correct
* another burst of corrupted bits.
*
* There is no parity check, so it is required to check all information
* elements of each message. Messages that contain signals or digits are
* protected by repeating the digits in the information element.
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "stdint.h"
/* To encode NMT message: (MSB first)
* Use input with 9 bytes, the last byte must be 0x00.
* Use output with 18 bytes, ignore the last four (lower) bits of last byte.
* Use length of 72.
*/
void hagelbarger_encode(const uint8_t *input, uint8_t *output, int length)
{
uint8_t reg = 0x00, data, check;
int i;
for (i = 0; i < length; i++) {
/* get data from input (MSB first) */
data = (input[i / 8] >> (7 - (i & 7))) & 1;
/* push data into shift register (LSB first) */
reg = (reg << 1) | data;
/* get data bit from register */
data = (reg >> 6) & 1;
/* calc check bit from register */
check = (reg + (reg >> 3) + 1) & 1;
/* put check bit and data bit to output (MSB first) */
output[i / 4] = (output[i / 4] << 2) | (check << 1) | data;
}
}
/* To decode NMT message: (MSB first)
* Use input with 19 bytes, the unused last 12 (lower) bits must be zero.
* Use output with 8 bytes.
* Use length of 64.
*/
void hagelbarger_decode(const uint8_t *input, uint8_t *output, int length)
{
uint16_t reg_data = 0x00, reg_check = 0x00, data, check, r_parity, s_parity;
int i;
length += 10;
for (i = 0; i < length; i++) {
/* get check bit from input (MSB first) */
check = (input[i / 4] >> (7 - (i & 3) * 2)) & 1;
/* get data bit from input (MSB first) */
data = (input[i / 4] >> (6 - (i & 3) * 2)) & 1;
/* push check bit into shift register (LSB first) */
reg_check = (reg_check << 1) | check;
/* push data bit into shift register (LSB first) */
reg_data = (reg_data << 1) | data;
/* calculate parity */
r_parity = (reg_data + (reg_data >> 3) + (reg_check >> 6) + 1) & 1;
s_parity = ((reg_data >> 3) + (reg_data >> 6) + (reg_check >> 9) + 1) & 1;
/* flip message bit, if both parity checks fail */
/* use 4th bit that will be shifted to 5th bit next loop */
if (r_parity && s_parity)
reg_data ^= 0x0008;
/* put message bit to output (MSB first) */
if (i >= 10)
output[(i - 10) / 8] = (output[(i - 10) / 8] << 1) | ((reg_data >> 4) & 1);
}
}

4
src/nmt/hagelbarger.h Normal file
View File

@ -0,0 +1,4 @@
void hagelbarger_encode(const uint8_t *input, uint8_t *output, int length);
void hagelbarger_decode(const uint8_t *input, uint8_t *output, int length);

View File

@ -829,15 +829,21 @@ static void rx_mo_dialing(nmt_t *nmt, frame_t *frame)
/* max digits received */
if (len + 1 == sizeof(nmt->dialing))
break;
/* received odd digit, but be already have odd number of digits */
if ((len & 1)) {
if (nmt->rx_frame_count > 1)
/* received odd digit, but be already have odd number of digits */
if (nmt->rx_frame_count > 1) /* we lost even digit */
goto missing_digit;
break;
} else if (len) {
if (nmt->rx_frame_count > 3)
} else if (len) { /* complain only after first digit */
/* received odd digit, and we have even number of digits */
if (nmt->rx_frame_count > 3) /* we lost even digit */
goto missing_digit;
}
if ((frame->digit >> 12) != 0x00) /* digit 0x0 0x0, x, x, x */
goto not_right_position;
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf))
goto not_consistent_digit;
nmt->dialing[len] = nmt_value2digit(frame->digit);
nmt->dialing[len + 1] = '\0';
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (odd) digit %c.\n", nmt->dialing[len]);
@ -857,15 +863,24 @@ static void rx_mo_dialing(nmt_t *nmt, frame_t *frame)
/* max digits received */
if (len + 1 == sizeof(nmt->dialing))
break;
/* received odd digit, but be already have odd number of digits */
/* received even digit, but no digit yet, so we lost first odd digit */
if (!len)
goto missing_digit;
if (!(len & 1)) {
if (len && nmt->rx_frame_count > 1)
/* received even digit, but be already have even number of digits */
if (nmt->rx_frame_count > 1) /* we lost odd digit */
goto missing_digit;
break;
} else {
if (nmt->rx_frame_count > 3)
/* received even digit, and we have odd number of digits */
if (nmt->rx_frame_count > 3) /* we lost odd digit */
goto missing_digit;
}
if ((frame->digit >> 12) != 0xff) /* digit 0xf 0xf, x, x, x */
goto not_right_position;
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf))
goto not_consistent_digit;
nmt->dialing[len] = nmt_value2digit(frame->digit);
nmt->dialing[len + 1] = '\0';
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (even) digit %c.\n", nmt->dialing[len]);
@ -906,6 +921,15 @@ static void rx_mo_dialing(nmt_t *nmt, frame_t *frame)
missing_digit:
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Missing digit, aborting.\n");
nmt_release(nmt);
return;
not_right_position:
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Position information of digit does not match, ignoring due to corrupt frame.\n");
return;
not_consistent_digit:
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Digit repetition in frame does not match, ignoring due to corrupt frame.\n");
return;
}
static void tx_mo_complete(nmt_t *nmt, frame_t *frame)
@ -1102,6 +1126,13 @@ static void rx_mt_autoanswer(nmt_t *nmt, frame_t *frame)
break;
if (!match_subscriber(trans, frame))
break;
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
break;
}
if ((frame->line_signal & 0xf) != 12)
break;
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received acknowledge to autoanswer.\n");
@ -1153,6 +1184,13 @@ static void rx_mt_ringing(nmt_t *nmt, frame_t *frame)
break;
if (!match_subscriber(trans, frame))
break;
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
break;
}
if ((frame->line_signal & 0xf) != 14)
break;
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received 'answer' from phone.\n");
@ -1240,6 +1278,13 @@ static void rx_mt_release(nmt_t *nmt, frame_t *frame)
break;
if (!match_subscriber(trans, frame))
break;
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
break;
}
if ((frame->line_signal & 0xf) != 1)
break;
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received release guard.\n");
@ -1312,6 +1357,13 @@ static void rx_active(nmt_t *nmt, frame_t *frame)
break;
if (!match_subscriber(trans, frame))
break;
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
break;
}
switch ((frame->line_signal & 0xf)) {
case 5:
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Register Recall is not supported.\n");
@ -1344,6 +1396,15 @@ static void rx_active(nmt_t *nmt, frame_t *frame)
break;
if ((nmt->mft_num & 1))
break;
if ((frame->digit >> 12) != 0x00) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Position information of digit does not match, ignoring due to corrupt frame.\n");
break;
}
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Digit repetition in frame does not match, ignoring due to corrupt frame.\n");
break;
}
digit = nmt_value2digit(frame->digit);
dtmf_set_tone(&nmt->dtmf, digit);
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (odd) digit %c.\n", digit);
@ -1358,6 +1419,15 @@ static void rx_active(nmt_t *nmt, frame_t *frame)
break;
if (!(nmt->mft_num & 1))
break;
if ((frame->digit >> 12) != 0xff) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Position information of digit does not match, ignoring due to corrupt frame.\n");
break;
}
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Digit repetition in frame does not match, ignoring due to corrupt frame.\n");
break;
}
digit = nmt_value2digit(frame->digit);
dtmf_set_tone(&nmt->dtmf, digit);
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (even) digit %c.\n", digit);
@ -1431,6 +1501,13 @@ void nmt_receive_frame(nmt_t *nmt, const char *bits, double quality, double leve
return;
if (!match_subscriber(nmt->trans, &frame))
return;
if (((frame.line_signal >> 16) & 0xf) != ((frame.line_signal >> 12) & 0xf)
|| ((frame.line_signal >> 12) & 0xf) != ((frame.line_signal >> 8) & 0xf)
|| ((frame.line_signal >> 8) & 0xf) != ((frame.line_signal >> 4) & 0xf)
|| ((frame.line_signal >> 4) & 0xf) != (frame.line_signal & 0xf)) {
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
return;
}
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received clearing by mobile phone in state %s.\n", nmt_state_name(nmt->state));
nmt_new_state(nmt, STATE_MO_RELEASE);
nmt->tx_frame_count = 0;

View File

@ -7,7 +7,8 @@ noinst_PROGRAMS = \
test_emphasis \
test_dms \
test_sms \
test_performance
test_performance \
test_hagelbarger
test_filter_SOURCES = test_filter.c dummy.c
@ -77,3 +78,10 @@ test_performance_LDADD = \
$(SOAPY_LIBS) \
-lm
test_hagelbarger_SOURCES = \
$(top_builddir)/src/nmt/hagelbarger.c \
test_hagelbarger.c
test_hagelbarger_LDADD = \
$(COMMON_LA)

View File

@ -0,0 +1,29 @@
#include "stdio.h"
#include "stdint.h"
#include "string.h"
#include "../nmt/hagelbarger.h"
int main(void)
{
uint8_t message[9] = "JollyRog", code[20];
printf("Message: %s\n", message);
/* clean tail at code bit 72 and above */
memset(code, 0, sizeof(code));
/* encode message */
hagelbarger_encode(message, code, 72);
/* corrupt data */
code[0] ^= 0xfc;
code[3] ^= 0xfc;
code[7] ^= 0xfc;
/* decode */
hagelbarger_decode(code, message, 64);
printf("Decoded: %s (must be the same as above)\n", message);
return 0;
}