1123 lines
28 KiB
C
1123 lines
28 KiB
C
/* NMT frame transcoding
|
|
*
|
|
* (C) 2016 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 <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include "../common/debug.h"
|
|
#include "../common/timer.h"
|
|
#include "nmt.h"
|
|
#include "frame.h"
|
|
|
|
uint64_t nmt_encode_channel(int channel, int power)
|
|
{
|
|
uint64_t value = 0;
|
|
|
|
if (channel >= 200) {
|
|
value |= 0x800;
|
|
channel -= 200;
|
|
}
|
|
if (channel >= 100) {
|
|
value |= 0x100;
|
|
channel -= 100;
|
|
}
|
|
value |= channel % 10;
|
|
value |= (channel / 10) << 4;
|
|
value |= power << 9;
|
|
|
|
return value;
|
|
}
|
|
|
|
int nmt_decode_channel(uint64_t value, int *channel, int *power)
|
|
{
|
|
if ((value & 0x00f) > 0x009)
|
|
return -1;
|
|
if ((value & 0x0f0) > 0x090)
|
|
return -1;
|
|
|
|
*channel = (value & 0x00f) +
|
|
((value & 0x0f0) >> 4) * 10 +
|
|
((value & 0x100) >> 8) * 100 +
|
|
((value & 0x800) >> 11) * 200;
|
|
*power = (value & 0x600) >> 9;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nmt_value2digits(uint64_t value, char *digits, int num)
|
|
{
|
|
int digit, i;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
digit = (value >> ((num - 1 - i) << 2)) & 0xf;
|
|
if (digit == 10)
|
|
digits[i] = '0';
|
|
else if (digit == 0 || digit > 10)
|
|
digits[i] = '?';
|
|
else
|
|
digits[i] = digit + '0';
|
|
}
|
|
}
|
|
|
|
uint64_t nmt_digits2value(const char *digits, int num)
|
|
{
|
|
int digit, i;
|
|
uint64_t value = 0;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
value <<= 4;
|
|
digit = *digits++;
|
|
if (digit >= '1' && digit <= '9')
|
|
value |= digit - '0';
|
|
else
|
|
value |= 10;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
char nmt_value2digit(uint64_t value)
|
|
{
|
|
return "D1234567890*#ABC"[value & 0x0000f];
|
|
}
|
|
|
|
uint16_t nmt_encode_area_no(uint8_t area_no)
|
|
{
|
|
switch (area_no) {
|
|
case 1:
|
|
return 0x3f3;
|
|
case 2:
|
|
return 0x3f4;
|
|
case 3:
|
|
return 0x3f5;
|
|
case 4:
|
|
return 0x3f6;
|
|
default:
|
|
return 0x000;
|
|
}
|
|
}
|
|
|
|
/* convert given number to caller ID frame with given index
|
|
* return next index */
|
|
int nmt_encode_a_number(frame_t *frame, int index, enum number_type type, const char *number)
|
|
{
|
|
int number_offset = 0;
|
|
int number_len = strlen(number);
|
|
int nframes;
|
|
uint8_t sum, ntype = 0, digit;
|
|
int i, shift;
|
|
|
|
/* number of frames
|
|
* 0..5 digits need one frame, 6..12 digits need two frames, ... */
|
|
nframes = (number_len + 8) / 7;
|
|
|
|
/* cycle index */
|
|
index %= nframes;
|
|
|
|
/* number offset for second frame is 5, and then additional 7 for the following frames */
|
|
if (index)
|
|
number_offset = index * 7 - 2;
|
|
|
|
/* encode */
|
|
frame->mt = NMT_MESSAGE_8;
|
|
frame->seq_number = index;
|
|
if (index == 0) {
|
|
/* number type */
|
|
switch (type) {
|
|
case TYPE_NOTAVAIL:
|
|
ntype = 3;
|
|
break;
|
|
case TYPE_ANONYMOUS:
|
|
ntype = 4;
|
|
break;
|
|
case TYPE_UNKNOWN:
|
|
ntype = 0;
|
|
break;
|
|
case TYPE_SUBSCRIBER:
|
|
ntype = 0;
|
|
break;
|
|
case TYPE_NATIONAL:
|
|
ntype = 1;
|
|
break;
|
|
case TYPE_INTERNATIONAL:
|
|
ntype = 2;
|
|
break;
|
|
}
|
|
/* first 5 digits */
|
|
frame->additional_info = ((nframes - 1) << 24) | (ntype << 20);
|
|
shift = 16;
|
|
} else {
|
|
/* next digits */
|
|
frame->additional_info = 0;
|
|
shift = 24;
|
|
}
|
|
for (i = number_offset; number[i] && shift >= 0; i++, shift -= 4) {
|
|
digit = number[i];
|
|
if (digit >= '1' && digit <= '9')
|
|
digit -= '0';
|
|
else if (digit == '0')
|
|
digit = 10;
|
|
else
|
|
digit = 13; /* '+' and illegal digits */
|
|
frame->additional_info |= (digit << shift);
|
|
}
|
|
|
|
/* checksum */
|
|
sum = (frame->seq_number << 4) | frame->additional_info >> 24;
|
|
sum += (frame->additional_info >> 16);
|
|
sum += (frame->additional_info >> 8);
|
|
sum += frame->additional_info;
|
|
frame->checksum = sum;
|
|
|
|
/* return next frame index or cycle to first frame */
|
|
if (++index == nframes)
|
|
index = 0;
|
|
return index;
|
|
}
|
|
|
|
/* NMT Doc 450-1 4.3.2 */
|
|
static struct nmt_frame {
|
|
enum nmt_mt message_type;
|
|
const char *digits;
|
|
enum nmt_direction direction;
|
|
int prefix;
|
|
const char *nr;
|
|
const char *description;
|
|
} nmt_frame[] = {
|
|
/* Define Digits Dir. Prefix Nr. Description */
|
|
{ NMT_MESSAGE_1a, "NNNPYYHHHHHHHHHH", MTX_TO_MS, 12, "1a", "Calling channel indication" },
|
|
{ NMT_MESSAGE_1b, "NNNPYYHHHHHHHHHH", MTX_TO_MS, 4, "1b", "Combined calling and traffic channel indication" },
|
|
{ NMT_MESSAGE_2a, "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2a", "Call to mobile subscriber on calling channel" },
|
|
{ NMT_MESSAGE_2b, "NNNPYYZXXXXXXnnn", MTX_TO_MS, 12, "2b", "Traffic channel allocation on calling channel" },
|
|
{ NMT_MESSAGE_2c, "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2c", "Queueing information to MS with priority on calling channel" },
|
|
{ NMT_MESSAGE_2d, "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2d", "Traffic channel scanning order on calling channel" },
|
|
{ NMT_MESSAGE_2f, "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2f", "Queuing information to ordinary MS" },
|
|
{ NMT_MESSAGE_3a, "NNNPYYZXXXXXXnnn", MTX_TO_MS, 5, "3a", "Traffic channel allocation on traffic channel" },
|
|
{ NMT_MESSAGE_3b, "NNNPYYZXXXXXXHHH", MTX_TO_MS, 5, "3b", "Identity request on traffic channel" },
|
|
{ NMT_MESSAGE_3c, "NNNPYYZXXXXXXnnn", MTX_TO_MS, 9, "3c", "Traffic channel allocation on traffic channel, short procedure" },
|
|
{ NMT_MESSAGE_4, "NNNPYYJJJJJJJHHH", MTX_TO_MS, 3, "4", "Free traffic channel indication" },
|
|
{ NMT_MESSAGE_5a, "NNNPYYZXXXXXXLLL", MTX_TO_MS, 6, "5a", "Line signal" },
|
|
{ NMT_MESSAGE_5b, "NNNPYYZXXXXXXLQQ", MTX_TO_MS, 6, "5b", "Line signal: Answer to coin-box" },
|
|
{ NMT_MESSAGE_6, "JJJPJJJJJJJJJJJJ", MTX_TO_XX, 0, "6", "Idle frame" },
|
|
{ NMT_MESSAGE_7, "NNNPYYCCCCCCCJJJ", MTX_TO_MS, 8, "7", "Authentication request" },
|
|
{ NMT_MESSAGE_8, "NNNPYYMHHHHHHHWW", MTX_TO_MS, 1, "8", "A-subscriber number" },
|
|
{ NMT_MESSAGE_10a, "NNNPZXXXXXXTJJJJ", MS_TO_MTX, 1, "10a", "Call acknowledgment from MS on calling channel (shortened frame)" },
|
|
{ NMT_MESSAGE_10b, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 1, "10b", "Seizure from ordinary MS and identity on traffic channel" },
|
|
{ NMT_MESSAGE_10c, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 6, "10c", "Seizure and identity from called MS on traffic channel" },
|
|
{ NMT_MESSAGE_11a, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 14, "11a", "Roaming updating seizure and identity on traffic channel" },
|
|
{ NMT_MESSAGE_11b, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 15, "11b", "Seizure and call achnowledgment on calling channel from MS with priority (shortened frame)" },
|
|
{ NMT_MESSAGE_12, "NNNPZXXXXXXTYKKK", MS_TO_MTX, 11, "12", "Seizure from coin-box on traffic channel" },
|
|
{ NMT_MESSAGE_13a, "NNNPZXXXXXXLLLLL", MS_TO_MTX, 8, "13a", "Line signal" },
|
|
{ NMT_MESSAGE_13b, "NNNPZXXXXXXLLLQQ", MS_TO_MTX, 8, "13b", "Line signal: Answer acknowledgment from coin box" },
|
|
{ NMT_MESSAGE_14a, "NNNPZXXXXXXSSSSS", MS_TO_MTX, 7, "14a", "Digit signal (1st, 3rd, 5th ........digit)" },
|
|
{ NMT_MESSAGE_14b, "NNNPZXXXXXXSSSSS", MS_TO_MTX, 7, "14b", "Digit signal (2nd, 4th, 6th ........digit)" },
|
|
{ NMT_MESSAGE_15, "JJJPJJJJJJJJJJJJ", XX_TO_MTX, 0, "15", "Idle frame" },
|
|
{ NMT_MESSAGE_16, "NNNPRRRRRRRRRRRR", MS_TO_MTX, 12, "16", "Signed response" },
|
|
{ NMT_MESSAGE_20_1, "NNNPYYZJJJAfffff", MTX_TO_BS, 15, "20", "Channel activation order" },
|
|
{ NMT_MESSAGE_20_2, "NNNPYYZJJJAJJJJJ", MTX_TO_BS, 15, "20", "Channel activation order" },
|
|
{ NMT_MESSAGE_20_3, "NNNPYYZJJJAfffff", MTX_TO_BS, 15, "20", "Channel activation order" },
|
|
{ NMT_MESSAGE_20_4, "NNNPYYZJJJAlllff", MTX_TO_BS, 15, "20", "Channel activation order" },
|
|
{ NMT_MESSAGE_20_5, "NNNPYYZJJJAlllJJ", MTX_TO_BS, 15, "20", "Channel activation order" },
|
|
{ NMT_MESSAGE_21b, "NNNPYYZJJJVJJnnn", MTX_TO_BS, 3, "21b", "Signal strength measurement order on data channel or idle or free marked traffic channel" },
|
|
{ NMT_MESSAGE_21c, "NNNPYYZJJJVJJnnn", MTX_TO_BS, 5, "21c", "Signal strength measurement order on traffic actually used" },
|
|
{ NMT_MESSAGE_22, "NNNPYYZJJJVVVVVV", MTX_TO_BS, 14, "22", "Order management/maintenance order on idle channel or data channel" },
|
|
{ NMT_MESSAGE_25_1, "NNNPZJJAJJJJJJJJ", BS_TO_MTX, 9, "25", "Channel status information" },
|
|
{ NMT_MESSAGE_25_2, "NNNPZJJAJJJfllJJ", BS_TO_MTX, 9, "25", "Channel status information" },
|
|
{ NMT_MESSAGE_25_3, "NNNPZJJAJJJJllJJ", BS_TO_MTX, 9, "25", "Channel status information" },
|
|
{ NMT_MESSAGE_25_4, "NNNPZJJAJJJcccJJ", BS_TO_MTX, 9, "25", "Channel status information" },
|
|
{ NMT_MESSAGE_26, "NNNPZJJnnnrrrrrr", BS_TO_MTX, 2, "26", "Signal strength measurement result" },
|
|
{ NMT_MESSAGE_27, "NNNPZJJVVVVJJJJJ", BS_TO_MTX, 4, "27", "Response on other management/maintenance order on idle channel or data channel" },
|
|
{ NMT_MESSAGE_28, "NNNPZJJVVVVJJJJJ", BS_TO_MTX, 13, "28", "Other maintenance information from BS" },
|
|
{ NMT_MESSAGE_30, "NNNPYYJJJJJJJHHH", MTX_TO_MS, 10, "30", "Test channel indication" },
|
|
{ NMT_MESSAGE_UKN_MTX, "---P------------", MTX_TO_XX, 0, "", "illegal (Spare)" },
|
|
{ NMT_MESSAGE_UKN_B, "---P------------", XX_TO_MTX, 0, "", "illegal (Spare)" },
|
|
{ 0, NULL, 0, 0, NULL, NULL }
|
|
};
|
|
|
|
/* store actual number of frames for run-time range check */
|
|
static int num_frames;
|
|
|
|
const char *nmt_frame_name(enum nmt_mt mt)
|
|
{
|
|
if (mt < 0 || mt >= num_frames)
|
|
return "invalid";
|
|
return nmt_frame[mt].nr;
|
|
}
|
|
|
|
static const char *param_integer(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
sprintf(result, "%" PRIu64, value);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_hex(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
sprintf(result, "0x%" PRIx64, value);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_channel_no(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
int rc, channel, power;
|
|
|
|
rc = nmt_decode_channel(value, &channel, &power);
|
|
if (rc < 0)
|
|
sprintf(result, "invalid(%" PRIu64 ")", value);
|
|
else
|
|
sprintf(result, "channel=%d power=%d", channel, power);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_country(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
|
|
switch (value) {
|
|
case 0:
|
|
return "no additional info";
|
|
case 4:
|
|
return "Iceland";
|
|
case 5:
|
|
return "Denmark";
|
|
case 6:
|
|
return "Sweden";
|
|
case 7:
|
|
return "Norway";
|
|
case 8:
|
|
return "Finland";
|
|
case 9:
|
|
return "nordic country";
|
|
case 14:
|
|
return "additional info";
|
|
case 15:
|
|
return "information to/from BS";
|
|
default:
|
|
sprintf(result, "%" PRIu64 " (unknown)", value);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
static const char *param_number(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
|
|
nmt_value2digits(value, result, ndigits);
|
|
result[ndigits] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_ta(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
|
|
nmt_value2digits(value, result, ndigits);
|
|
result[ndigits] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_line_signal(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[64], *desc = "Spare";
|
|
|
|
if (direction == MTX_TO_MS) {
|
|
switch (value & 0xf) {
|
|
case 0:
|
|
desc = "Answer to coin-box";
|
|
break;
|
|
case 3:
|
|
desc = "Proceed to send unencrypted digits";
|
|
break;
|
|
case 4:
|
|
desc = "Acknowledge MFT converter in";
|
|
break;
|
|
case 5:
|
|
desc = "Switch compandor in";
|
|
break;
|
|
case 6:
|
|
desc = "Address complete";
|
|
break;
|
|
case 7:
|
|
desc = "Switch compandor out";
|
|
break;
|
|
case 9:
|
|
desc = "Ringing order";
|
|
break;
|
|
case 10:
|
|
desc = "Acknowledge MFT converter out";
|
|
break;
|
|
case 11:
|
|
desc = "Proceed to send enctrypted digits";
|
|
break;
|
|
case 13:
|
|
desc = "Clearing, call transfer activated";
|
|
break;
|
|
case 15:
|
|
desc = "Clearing, call transfer not activated";
|
|
break;
|
|
}
|
|
} else {
|
|
switch (value & 0xf) {
|
|
case 1:
|
|
desc = "Clearing, release guard";
|
|
break;
|
|
case 2:
|
|
desc = "Answer acknowledgment, (coin-box)";
|
|
break;
|
|
case 5:
|
|
desc = "Register recall ";
|
|
break;
|
|
case 7:
|
|
desc = "MFT converter out acknowledge";
|
|
break;
|
|
case 8:
|
|
desc = "MFT converter in";
|
|
break;
|
|
case 14:
|
|
desc = "Answer";
|
|
break;
|
|
}
|
|
}
|
|
|
|
sprintf(result, "L(%" PRIu64 ") %s", value & 0xf, desc);
|
|
return result;
|
|
}
|
|
|
|
static const char *param_digit(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
|
|
if ((value & 0xf0000) != 0x00000 && (value & 0xf0000) != 0xf0000)
|
|
return "Invalid digit";
|
|
if ((value & 0xf0000) != ((value & 0x0f000) << 4)
|
|
|| (value & 0x00f00) != ((value & 0x000f0) << 4)
|
|
|| (value & 0x000f0) != ((value & 0x0000f) << 4))
|
|
return "Inconsistent digit";
|
|
|
|
result[0] = nmt_value2digit(value);
|
|
result[1] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_supervisory(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
switch (value) {
|
|
case 0:
|
|
return "Reserved";
|
|
case 3:
|
|
return "Frequency 1";
|
|
case 12:
|
|
return "Frequency 2";
|
|
case 9:
|
|
return "Frequency 3";
|
|
case 6:
|
|
return "Frequency 4";
|
|
default:
|
|
return "Invalid";
|
|
}
|
|
}
|
|
|
|
static const char *param_password(uint64_t value, int ndigits, enum nmt_direction direction)
|
|
{
|
|
static char result[32];
|
|
|
|
nmt_value2digits(value, result, ndigits);
|
|
result[ndigits] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
static struct nmt_parameter {
|
|
char digit;
|
|
const char *description;
|
|
const char *(*decoder)(uint64_t value, int ndigits, enum nmt_direction direction);
|
|
} nmt_parameter[] = {
|
|
{ 'N', "Channel No.", param_channel_no },
|
|
{ 'n', "TC No.", param_channel_no },
|
|
{ 'Y', "Traffic area", param_ta },
|
|
{ 'Z', "Mobile subscriber country", param_country },
|
|
{ 'X', "Mobile subscriber No.", param_number },
|
|
{ 'Q', "Tariff class", param_integer },
|
|
{ 'L', "Line signal", param_line_signal },
|
|
{ 'S', "Digit signals", param_digit },
|
|
{ 'J', "Idle information", param_hex },
|
|
{ 'A', "Channel activation", param_hex },
|
|
{ 'V', "Management order", param_hex },
|
|
{ 'r', "Measurement results", param_hex },
|
|
{ 'P', "Prefix", param_integer },
|
|
{ 'f', "Supervisory signal", param_supervisory },
|
|
{ 'K', "Mobile subscriber password", param_password },
|
|
{ 'T', "Area info", param_hex },
|
|
{ 'H', "Additional info", param_hex },
|
|
{ 'C', "Random challenge", param_hex },
|
|
{ 'R', "Signed response", param_hex },
|
|
{ 'l', "Limit strength evaluation", param_hex },
|
|
{ 'c', "c", param_hex },
|
|
{ 'M', "Sequence Number", param_integer },
|
|
{ 'W', "Checksum", param_hex },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* Depending on P-value, direction and additional info, frame index (used for
|
|
* nmt_frame[]) is decoded.
|
|
*/
|
|
enum nmt_mt decode_frame_mt(const uint8_t *digits, enum nmt_direction direction, int callack)
|
|
{
|
|
if (direction == MS_TO_MTX || direction == BS_TO_MTX || direction == XX_TO_MTX) {
|
|
/* MS/BS TO MTX */
|
|
switch (digits[3]) {
|
|
case 0:
|
|
return NMT_MESSAGE_15;
|
|
case 1:
|
|
if (callack)
|
|
return NMT_MESSAGE_10a;
|
|
return NMT_MESSAGE_10b;
|
|
case 2:
|
|
return NMT_MESSAGE_26;
|
|
case 3:
|
|
break;
|
|
case 4:
|
|
return NMT_MESSAGE_27;
|
|
case 5:
|
|
break;
|
|
case 6:
|
|
return NMT_MESSAGE_10c;
|
|
case 7:
|
|
if (digits[11] == 0)
|
|
return NMT_MESSAGE_14a;
|
|
if (digits[11] == 15)
|
|
return NMT_MESSAGE_14b;
|
|
return -1;
|
|
case 8:
|
|
if (digits[11] == 2)
|
|
return NMT_MESSAGE_13b;
|
|
return NMT_MESSAGE_13a;
|
|
case 9:
|
|
switch((digits[13] << 8) + (digits[14] << 4) + digits[15]) {
|
|
case 2:
|
|
case 6:
|
|
return NMT_MESSAGE_25_2;
|
|
case 14:
|
|
return NMT_MESSAGE_25_3;
|
|
case 7:
|
|
case 8:
|
|
return NMT_MESSAGE_25_4;
|
|
default:
|
|
return NMT_MESSAGE_25_1;
|
|
}
|
|
case 10:
|
|
break;
|
|
case 11:
|
|
return NMT_MESSAGE_12;
|
|
case 12:
|
|
return NMT_MESSAGE_16;
|
|
case 13:
|
|
return NMT_MESSAGE_28;
|
|
case 14:
|
|
return NMT_MESSAGE_11a;
|
|
case 15:
|
|
return NMT_MESSAGE_11b;
|
|
}
|
|
return NMT_MESSAGE_UKN_B;
|
|
} else {
|
|
/* MTX to MS/BS */
|
|
switch (digits[3]) {
|
|
case 0:
|
|
return NMT_MESSAGE_6;
|
|
case 1:
|
|
return NMT_MESSAGE_8;
|
|
case 2:
|
|
break;
|
|
case 3:
|
|
if (digits[6] == 15)
|
|
return NMT_MESSAGE_21b;
|
|
return NMT_MESSAGE_4;
|
|
case 4:
|
|
return NMT_MESSAGE_1b;
|
|
case 5:
|
|
if (digits[6] == 15)
|
|
return NMT_MESSAGE_21c;
|
|
switch((digits[13] << 8) + (digits[14] << 4) + digits[15]) {
|
|
case 0x3f3:
|
|
case 0x3f4:
|
|
case 0x3f5:
|
|
case 0x3f6:
|
|
case 0x000:
|
|
return NMT_MESSAGE_3b;
|
|
default:
|
|
return NMT_MESSAGE_3a;
|
|
}
|
|
case 6:
|
|
if (digits[13] == 0)
|
|
return NMT_MESSAGE_5b;
|
|
return NMT_MESSAGE_5a;
|
|
case 7:
|
|
break;
|
|
case 8:
|
|
return NMT_MESSAGE_7;
|
|
case 9:
|
|
return NMT_MESSAGE_3c;
|
|
case 10:
|
|
return NMT_MESSAGE_30;
|
|
case 11:
|
|
break;
|
|
case 12:
|
|
/* no subscriber */
|
|
if (digits[6] == 0)
|
|
return NMT_MESSAGE_1a;
|
|
/* battery saving */
|
|
if (digits[6] == 14)
|
|
return NMT_MESSAGE_1a;
|
|
/* info to BS (should not happen here) */
|
|
if (digits[6] == 15)
|
|
return NMT_MESSAGE_1a;
|
|
switch((digits[13] << 8) + (digits[14] << 4) + digits[15]) {
|
|
case 0x3f3:
|
|
case 0x3f4:
|
|
case 0x3f5:
|
|
case 0x3f6:
|
|
case 0x000:
|
|
return NMT_MESSAGE_2a;
|
|
case 0x3f0:
|
|
return NMT_MESSAGE_2f;
|
|
case 0x3f1:
|
|
return NMT_MESSAGE_2c;
|
|
case 0x3f2:
|
|
return NMT_MESSAGE_2d;
|
|
default:
|
|
return NMT_MESSAGE_2b;
|
|
}
|
|
case 13:
|
|
break;
|
|
case 14:
|
|
if (digits[13] != 15)
|
|
break;
|
|
return NMT_MESSAGE_22;
|
|
case 15:
|
|
if (digits[13] != 15)
|
|
break;
|
|
switch (digits[10]) {
|
|
case 3:
|
|
return NMT_MESSAGE_20_1;
|
|
case 6:
|
|
case 13:
|
|
return NMT_MESSAGE_20_3;
|
|
case 7:
|
|
case 14:
|
|
return NMT_MESSAGE_20_4;
|
|
case 15:
|
|
return NMT_MESSAGE_20_5;
|
|
default:
|
|
return NMT_MESSAGE_20_2;
|
|
}
|
|
}
|
|
return NMT_MESSAGE_UKN_MTX;
|
|
}
|
|
}
|
|
|
|
int init_frame(void)
|
|
{
|
|
int i, j, k;
|
|
char digit;
|
|
|
|
/* check if all digits actually exist */
|
|
for (i = 0; nmt_frame[i].digits; i++) {
|
|
/* check mesage type */
|
|
if (nmt_frame[i].message_type != i) {
|
|
PDEBUG(DFRAME, DEBUG_ERROR, "Message type at message index #%d does not have a value of %d, but has %d, please fix!\n", i, i + 1, nmt_frame[i].message_type);
|
|
return -1;
|
|
}
|
|
/* check IEs */
|
|
for (j = 0; j < 16; j++) {
|
|
digit = nmt_frame[i].digits[j];
|
|
if (digit == '-')
|
|
continue;
|
|
for (k = 0; nmt_parameter[k].digit; k++) {
|
|
if (nmt_parameter[k].digit == digit)
|
|
break;
|
|
}
|
|
if (!nmt_parameter[k].digit) {
|
|
PDEBUG(DFRAME, DEBUG_ERROR, "Digit '%c' in message index %d does not exist, please fix!\n", digit, i);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
num_frames = i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* decode 16 digits frame */
|
|
static void disassemble_frame(frame_t *frame, const uint8_t *digits, enum nmt_direction direction, int callack)
|
|
{
|
|
enum nmt_mt mt;
|
|
int i, j, ndigits;
|
|
char digit;
|
|
uint64_t value;
|
|
|
|
memset(frame, 0, sizeof(*frame));
|
|
|
|
/* message type of frame */
|
|
mt = decode_frame_mt(digits, direction, callack);
|
|
frame->mt = mt;
|
|
|
|
/* update direction */
|
|
direction = nmt_frame[mt].direction;
|
|
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "Decoding %s %s %s\n", nmt_dir_name(direction), nmt_frame[mt].nr, nmt_frame[mt].description);
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
digit = nmt_frame[mt].digits[i];
|
|
if (digit == '-')
|
|
continue;
|
|
value = digits[i];
|
|
ndigits = 1;
|
|
for (j = i + 1; j < 16; j++) {
|
|
if (nmt_frame[mt].digits[j] != digit)
|
|
break;
|
|
value = (value << 4) | digits[j];
|
|
ndigits++;
|
|
i++;
|
|
}
|
|
switch (digit) {
|
|
case 'N':
|
|
frame->channel_no = value;
|
|
break;
|
|
case 'n':
|
|
frame->tc_no = value;
|
|
break;
|
|
case 'Y':
|
|
frame->traffic_area = value;
|
|
break;
|
|
case 'Z':
|
|
frame->ms_country = value;
|
|
break;
|
|
case 'X':
|
|
frame->ms_number = value;
|
|
break;
|
|
case 'Q':
|
|
frame->tariff_class = value;
|
|
break;
|
|
case 'L':
|
|
frame->line_signal = value;
|
|
break;
|
|
case 'S':
|
|
frame->digit = value;
|
|
break;
|
|
case 'J':
|
|
frame->idle = value;
|
|
break;
|
|
case 'A':
|
|
frame->chan_act = value;
|
|
break;
|
|
case 'V':
|
|
frame->meas_order = value;
|
|
break;
|
|
case 'r':
|
|
frame->meas = value;
|
|
break;
|
|
case 'P':
|
|
frame->prefix = value;
|
|
break;
|
|
case 'f':
|
|
frame->supervisory = value;
|
|
break;
|
|
case 'K':
|
|
frame->ms_password = value;
|
|
break;
|
|
case 'T':
|
|
frame->area_info = value;
|
|
break;
|
|
case 'H':
|
|
frame->additional_info = value;
|
|
break;
|
|
case 'C':
|
|
frame->rand = value;
|
|
break;
|
|
case 'R':
|
|
frame->sres = value;
|
|
break;
|
|
case 'l':
|
|
frame->limit_strength_eval = value;
|
|
break;
|
|
case 'c':
|
|
frame->c = value;
|
|
break;
|
|
case 'M':
|
|
frame->seq_number = value;
|
|
break;
|
|
case 'W':
|
|
frame->checksum = value;
|
|
break;
|
|
default:
|
|
PDEBUG(DFRAME, DEBUG_ERROR, "Digit '%c' does not exist, please fix!\n", digit);
|
|
abort();
|
|
}
|
|
if (debuglevel <= DEBUG_DEBUG) {
|
|
for (j = 0; nmt_parameter[j].digit; j++) {
|
|
if (nmt_parameter[j].digit == digit) {
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, " %c: %s\n", digit, nmt_parameter[j].decoder(value, ndigits, direction));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (debuglevel <= DEBUG_DEBUG) {
|
|
char debug_digits[17];
|
|
|
|
for (i = 0; i < 16; i++)
|
|
debug_digits[i] = "0123456789abcdef"[digits[i]];
|
|
debug_digits[i] = '\0';
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "%s\n", nmt_frame[mt].digits);
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "%s\n", debug_digits);
|
|
}
|
|
}
|
|
|
|
/* encode 16 digits frame */
|
|
static void assemble_frame(frame_t *frame, uint8_t *digits, int debug)
|
|
{
|
|
enum nmt_mt mt;
|
|
int i, j;
|
|
char digit;
|
|
uint64_t value;
|
|
enum nmt_direction direction;
|
|
|
|
mt = frame->mt;
|
|
|
|
if (mt >= num_frames) {
|
|
PDEBUG(DFRAME, DEBUG_ERROR, "Frame mt %d out of range (0..%d), please fix!\n", mt, num_frames - 1);
|
|
abort();
|
|
}
|
|
|
|
/* set prefix of frame */
|
|
frame->prefix = nmt_frame[mt].prefix;
|
|
|
|
/* retrieve direction */
|
|
direction = nmt_frame[mt].direction;
|
|
|
|
if (debug)
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "Coding %s %s %s\n", nmt_dir_name(direction), nmt_frame[mt].nr, nmt_frame[mt].description);
|
|
|
|
for (i = 15; i >= 0; i--) {
|
|
digit = nmt_frame[mt].digits[i];
|
|
if (digit == '-') {
|
|
digits[i] = 0;
|
|
continue;
|
|
}
|
|
switch (digit) {
|
|
case 'N':
|
|
value = frame->channel_no;
|
|
break;
|
|
case 'n':
|
|
value = frame->tc_no;
|
|
break;
|
|
case 'Y':
|
|
value = frame->traffic_area;
|
|
break;
|
|
case 'Z':
|
|
value = frame->ms_country;
|
|
break;
|
|
case 'X':
|
|
value = frame->ms_number;
|
|
break;
|
|
case 'Q':
|
|
value = frame->tariff_class;
|
|
break;
|
|
case 'L':
|
|
value = frame->line_signal;
|
|
break;
|
|
case 'S':
|
|
value = frame->digit;
|
|
break;
|
|
case 'J':
|
|
value = frame->idle;
|
|
break;
|
|
case 'A':
|
|
value = frame->chan_act;
|
|
break;
|
|
case 'V':
|
|
value = frame->meas_order;
|
|
break;
|
|
case 'r':
|
|
value = frame->meas;
|
|
break;
|
|
case 'P':
|
|
value = frame->prefix;
|
|
break;
|
|
case 'f':
|
|
value = frame->supervisory;
|
|
break;
|
|
case 'K':
|
|
value = frame->ms_password;
|
|
break;
|
|
case 'T':
|
|
value = frame->area_info;
|
|
break;
|
|
case 'H':
|
|
value = frame->additional_info;
|
|
break;
|
|
case 'C':
|
|
value = frame->rand;
|
|
break;
|
|
case 'R':
|
|
value = frame->sres;
|
|
break;
|
|
case 'l':
|
|
value = frame->limit_strength_eval;
|
|
break;
|
|
case 'c':
|
|
value = frame->c;
|
|
break;
|
|
case 'M':
|
|
value = frame->seq_number;
|
|
break;
|
|
case 'W':
|
|
value = frame->checksum;
|
|
break;
|
|
default:
|
|
PDEBUG(DFRAME, DEBUG_ERROR, "Digit '%c' does not exist, please fix!\n", digit);
|
|
abort();
|
|
}
|
|
|
|
digits[i] = (value & 0xf);
|
|
value >>= 4;
|
|
for (j = i - 1; j >= 0; j--) {
|
|
if (nmt_frame[mt].digits[j] != digit)
|
|
break;
|
|
digits[j] = (value & 0xf);
|
|
value >>= 4;
|
|
i--;
|
|
}
|
|
}
|
|
if (debug && debuglevel <= DEBUG_DEBUG) {
|
|
char debug_digits[17];
|
|
int ndigits;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
digit = nmt_frame[mt].digits[i];
|
|
if (digit == '-')
|
|
continue;
|
|
value = digits[i];
|
|
ndigits = 1;
|
|
for (j = i + 1; j < 16; j++) {
|
|
if (nmt_frame[mt].digits[j] != digit)
|
|
break;
|
|
value = (value << 4) | digits[j];
|
|
ndigits++;
|
|
i++;
|
|
}
|
|
for (j = 0; nmt_parameter[j].digit; j++) {
|
|
if (nmt_parameter[j].digit == digit) {
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, " %c: %s\n", digit, nmt_parameter[j].decoder(value, ndigits, direction));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 16; i++)
|
|
debug_digits[i] = "0123456789abcdef"[digits[i]];
|
|
debug_digits[i] = '\0';
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "%s\n", nmt_frame[mt].digits);
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "%s\n", debug_digits);
|
|
}
|
|
}
|
|
|
|
/* 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];
|
|
static char bits[166];
|
|
|
|
assemble_frame(frame, digits, debug);
|
|
encode_digits(digits, bits);
|
|
|
|
return bits;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
rc = decode_digits(digits, bits, callack);
|
|
if (rc < 0)
|
|
return rc;
|
|
disassemble_frame(frame, digits, direction, callack);
|
|
|
|
return 0;
|
|
}
|
|
|