forked from cellular-infrastructure/osmocom-analog
574 lines
15 KiB
C
574 lines
15 KiB
C
/* Radiocom 2000 frame transcoding
|
|
*
|
|
* (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 <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include "../libdebug/debug.h"
|
|
#include "../libhagelbarger/hagelbarger.h"
|
|
#include "frame.h"
|
|
|
|
static const char *param_hex(uint64_t value)
|
|
{
|
|
static char result[32];
|
|
sprintf(result, "0x%" PRIx64, value);
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *param_voie_rel(uint64_t value)
|
|
{
|
|
return (value) ? "Control Channel" : "Traffic Channel";
|
|
}
|
|
|
|
static const char *param_voie_sm(uint64_t value)
|
|
{
|
|
return (value) ? "Traffic Channel" : "Control Channel";
|
|
}
|
|
|
|
const char *param_agi(uint64_t value)
|
|
{
|
|
switch (value) {
|
|
case 0:
|
|
return "Prohibited control channel (no mobile allowed)";
|
|
case 1:
|
|
return "New registration prohibited (registered mobiles allowed)";
|
|
case 2:
|
|
return "Registration is reserved to test mobiles";
|
|
case 3:
|
|
return "Registration for nominal mobiles (home network)";
|
|
case 4:
|
|
return "Registration is reserved to special mobiles";
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
return "Registration permissible for all mobile station";
|
|
}
|
|
return "<invalid>";
|
|
}
|
|
|
|
const char *param_aga(uint64_t value)
|
|
{
|
|
switch (value) {
|
|
case 0:
|
|
return "Outgoing calls prohibited";
|
|
case 1:
|
|
return "Reserved (Outgoing calls prohibited)";
|
|
case 2:
|
|
return "Outgoing call reserved for privileged mobiles";
|
|
case 3:
|
|
return "Outgoing calls permissible";
|
|
}
|
|
return "<invalid>";
|
|
}
|
|
|
|
const char *param_power(uint64_t value)
|
|
{
|
|
switch (value) {
|
|
case 0:
|
|
return "Low";
|
|
case 1:
|
|
return "High";
|
|
}
|
|
return "<invalid>";
|
|
}
|
|
|
|
const char *param_crins(uint64_t value)
|
|
{
|
|
switch (value) {
|
|
case 0:
|
|
return "Finished or just registering";
|
|
case 1:
|
|
return "Localization impossible (queue full)";
|
|
case 2:
|
|
return "Mobile station temporarily disabled";
|
|
case 3:
|
|
return "Mobile station definitely disabled (WILL BRICK THE PHONE!)";
|
|
case 4:
|
|
return "Blocked localization (BS out of order)";
|
|
case 5:
|
|
case 6:
|
|
return "Reserved";
|
|
case 7:
|
|
return "Calling subscriber unknown";
|
|
}
|
|
return "<invalid>";
|
|
}
|
|
|
|
const char *param_invitation(uint64_t value)
|
|
{
|
|
switch (value) {
|
|
case 3:
|
|
return "to Answer";
|
|
case 10:
|
|
return "to Dial";
|
|
}
|
|
return "<unknown>";
|
|
}
|
|
|
|
static const char *param_digit(uint64_t value)
|
|
{
|
|
static char result[32];
|
|
switch (value) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
case 9:
|
|
sprintf(result, "'%c'", (int)value + '0');
|
|
return result;
|
|
case 10:
|
|
return "'*'";
|
|
case 11:
|
|
return "'#'";
|
|
case 12:
|
|
return "'A'";
|
|
case 13:
|
|
return "'B'";
|
|
case 14:
|
|
return "'C'";
|
|
case 15:
|
|
return "'D'";
|
|
}
|
|
return "<invalid>";
|
|
}
|
|
|
|
static struct r2000_element {
|
|
char element;
|
|
const char *name;
|
|
const char *(*decoder_rel)(uint64_t value); /* REL sends to SM */
|
|
const char *(*decoder_sm)(uint64_t value); /* SM sends to REL */
|
|
} r2000_element[] = {
|
|
{ 'V', "Channel Type", param_voie_rel, param_voie_sm },
|
|
{ 'C', "Channel", NULL, NULL },
|
|
{ 'R', "Relais", NULL, NULL },
|
|
{ 'M', "Message", NULL, NULL },
|
|
{ 'D', "Deport", NULL, NULL },
|
|
{ 'I', "AGI", param_agi, param_agi },
|
|
// { 'A', "AGA", param_aga, param_aga },
|
|
{ 'P', "power", param_power, param_power },
|
|
{ 'T', "taxe", NULL, NULL },
|
|
{ 't', "SM Type", param_hex, param_hex },
|
|
{ 'r', "SM Relais", NULL, NULL },
|
|
{ 'f', "SM Flotte", NULL, NULL },
|
|
{ 'm', "SM ID", NULL, NULL },
|
|
{ 'd', "Called Flotte", NULL, NULL },
|
|
{ 'c', "CRINS", param_crins, param_crins },
|
|
{ 'a', "Assign Channel", NULL, NULL },
|
|
{ 's', "Sequence Number", param_hex, param_hex },
|
|
{ 'i', "Invitation", param_invitation,param_invitation },
|
|
{ 'n', "NCONV", NULL, NULL },
|
|
{ '0', "1st Digit", param_digit, param_digit },
|
|
{ '1', "2nd Digit", param_digit, param_digit },
|
|
{ '2', "3rd Digit", param_digit, param_digit },
|
|
{ '3', "4th Digit", param_digit, param_digit },
|
|
{ '4', "5th Digit", param_digit, param_digit },
|
|
{ '5', "6th Digit", param_digit, param_digit },
|
|
{ '6', "7th Digit", param_digit, param_digit },
|
|
{ '7', "8th Digit", param_digit, param_digit },
|
|
{ '8', "9th Digit", param_digit, param_digit },
|
|
{ '9', "10th Digit", param_digit, param_digit },
|
|
{ '?', "Unknown", param_hex, param_hex },
|
|
{ 0, NULL, NULL, NULL }
|
|
};
|
|
|
|
static void print_element(char element, uint64_t value, int dir, int debug)
|
|
{
|
|
const char *(*decoder)(uint64_t value);
|
|
int i;
|
|
|
|
for (i = 0; r2000_element[i].element; i++) {
|
|
if (r2000_element[i].element == element)
|
|
break;
|
|
}
|
|
decoder = (dir == REL_TO_SM) ? r2000_element[i].decoder_rel : r2000_element[i].decoder_sm;
|
|
|
|
if (!r2000_element[i].element)
|
|
PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 " [Unknown]\n", element, value);
|
|
else if (!decoder)
|
|
PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 " [%s]\n", element, value, r2000_element[i].name);
|
|
else
|
|
PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 "=%s [%s]\n", element, value, decoder(value), r2000_element[i].name);
|
|
}
|
|
|
|
static void store_element(frame_t *frame, char element, uint64_t value)
|
|
{
|
|
switch(element) {
|
|
case 'V':
|
|
frame->voie = value;
|
|
break;
|
|
case 'C':
|
|
frame->channel = value;
|
|
break;
|
|
case 'R':
|
|
frame->relais = value;
|
|
break;
|
|
case 'M':
|
|
frame->message = value;
|
|
break;
|
|
case 'D':
|
|
frame->deport = value;
|
|
break;
|
|
case 'I':
|
|
frame->agi = value;
|
|
break;
|
|
case 'P':
|
|
frame->sm_power = value;
|
|
break;
|
|
case 'T':
|
|
frame->taxe = value;
|
|
break;
|
|
case 't':
|
|
frame->sm_type = value;
|
|
break;
|
|
case 'r':
|
|
frame->sm_relais = value;
|
|
break;
|
|
case 'f':
|
|
frame->sm_flotte = value;
|
|
break;
|
|
case 'm':
|
|
frame->sm_mor = value;
|
|
break;
|
|
case 'd':
|
|
frame->sm_mop_demandee = value;
|
|
break;
|
|
case 'c':
|
|
frame->crins = value;
|
|
break;
|
|
case 'a':
|
|
frame->chan_assign = value;
|
|
break;
|
|
case 's':
|
|
frame->sequence = value;
|
|
break;
|
|
case 'i':
|
|
frame->invitation = value;
|
|
break;
|
|
case 'n':
|
|
frame->nconv = value;
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
frame->digit[element - '0'] = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint64_t fetch_element(frame_t *frame, char element)
|
|
{
|
|
switch(element) {
|
|
case 'V':
|
|
return frame->voie;
|
|
case 'C':
|
|
return frame->channel;
|
|
case 'R':
|
|
return frame->relais;
|
|
case 'M':
|
|
return frame->message;
|
|
case 'D':
|
|
return frame->deport;
|
|
case 'I':
|
|
return frame->agi;
|
|
case 'P':
|
|
return frame->sm_power;
|
|
case 'T':
|
|
return frame->taxe;
|
|
case 't':
|
|
return frame->sm_type;
|
|
case 'r':
|
|
return frame->sm_relais;
|
|
case 'f':
|
|
return frame->sm_flotte;
|
|
case 'm':
|
|
return frame->sm_mor;
|
|
case 'd':
|
|
return frame->sm_mop_demandee;
|
|
case 'c':
|
|
return frame->crins;
|
|
case 'a':
|
|
return frame->chan_assign;
|
|
case 's':
|
|
return frame->sequence;
|
|
case 'i':
|
|
return frame->invitation;
|
|
case 'n':
|
|
return frame->nconv;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
return frame->digit[element - '0'];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct r2000_frame {
|
|
int dir;
|
|
uint8_t message;
|
|
const char *def;
|
|
const char *name;
|
|
} r2000_frame_def[] = {
|
|
/* V Channel-Relais---Msg--t--HomeRel--MobieID---------misc--------Supervisory----- */
|
|
/* messages REL->SM */
|
|
{ REL_TO_SM, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----ccc----DDDIII++---PT---", "INSCRIPTION ACK" }, /* inscription ack */
|
|
{ REL_TO_SM, 1, "V-CCCCCCCCRRRRRRRRRMMMMM----------------------------------------DDDIII++---PT---", "IDLE" }, /* broadcast */
|
|
{ REL_TO_SM, 2, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "PLEASE WAIT" }, /* waiting on CC */
|
|
{ REL_TO_SM, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN INCOMING"}, /* assign incoming call */
|
|
{ REL_TO_SM, 4, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN INCOMING (GROUP)"}, /* assign group call */
|
|
{ REL_TO_SM, 5, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN OUTGOING"}, /* assign outgoing call */
|
|
{ REL_TO_SM, 9, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "RELEASE ON CC" }, /* release call on CC */
|
|
{ REL_TO_SM, 16, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "IDENTITY REQ"}, /* request identity */
|
|
{ REL_TO_SM, 17, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----nnniiii----------------", "INVITATION"}, /* invitation */
|
|
{ REL_TO_SM, 24, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "RELEASE ON TC"}, /* release call */
|
|
{ REL_TO_SM, 26, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "SUSPEND REQ"}, /* suspend after dialing */
|
|
/* messages SM->REL */
|
|
{ SM_TO_REL, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "INSCRIPTION REQ" }, /* inscription */
|
|
{ SM_TO_REL, 1, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "CALL REQ (PRIVATE)" }, /* request call */
|
|
{ SM_TO_REL, 2, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmddddddddssss", "CALL REQ (GROUP)" }, /* request call */
|
|
{ SM_TO_REL, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "CALL REQ (PUBLIC)" }, /* request call */
|
|
{ SM_TO_REL, 6, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "RELEASE ON CC" }, /* release on CC */
|
|
{ SM_TO_REL, 16, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "IDENTITY ACK" }, /* identity response */
|
|
{ SM_TO_REL, 17, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "ANSWER" }, /* answer */
|
|
{ SM_TO_REL, 19, "V-CCCCCCCCRRRRRRRRRMMMMM1111000033332222555544447777666699998888", "DIAL 1..10" }, /* first 10 digits */
|
|
{ SM_TO_REL, 20, "V-CCCCCCCCRRRRRRRRRMMMMM1111000033332222555544447777666699998888", "DIAL 11..20" }, /* second 10 digits */
|
|
{ SM_TO_REL, 24, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "RELEASE ON TC" }, /* release call on TC */
|
|
{ SM_TO_REL, 26, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "SUSPEND ACK" }, /* release after dialing */
|
|
{ 0, 0, NULL, NULL }
|
|
};
|
|
|
|
static const char *get_frame_def(uint8_t message, int dir)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; r2000_frame_def[i].def; i++) {
|
|
if (r2000_frame_def[i].message == message && r2000_frame_def[i].dir == dir)
|
|
return r2000_frame_def[i].def;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char *r2000_dir_name(int dir)
|
|
{
|
|
return (dir == REL_TO_SM) ? "REL->SM" : "SM->REL";
|
|
}
|
|
|
|
const char *r2000_frame_name(int message, int dir)
|
|
{
|
|
static char result[32];
|
|
int i;
|
|
|
|
for (i = 0; r2000_frame_def[i].def; i++) {
|
|
if (r2000_frame_def[i].message == message && r2000_frame_def[i].dir == dir) {
|
|
sprintf(result, "%s (%d)", r2000_frame_def[i].name, message);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
sprintf(result, "UNKNOWN (%d)", message);
|
|
return result;
|
|
}
|
|
|
|
static void display_bits(const char *def, const uint8_t *message, int num, int debug)
|
|
{
|
|
char dispbits[num + 1];
|
|
int i;
|
|
|
|
if (debuglevel > debug)
|
|
return;
|
|
|
|
/* display bits */
|
|
if (def)
|
|
PDEBUG(DFRAME, debug, "%s\n", def);
|
|
for (i = 0; i < num; i++) {
|
|
dispbits[i] = ((message[i / 8] >> (7 - (i & 7))) & 1) + '0';
|
|
}
|
|
dispbits[i] = '\0';
|
|
PDEBUG(DFRAME, debug, "%s\n", dispbits);
|
|
}
|
|
|
|
static int dissassemble_frame(frame_t *frame, const uint8_t *message, int num)
|
|
{
|
|
int i;
|
|
const char *def;
|
|
uint64_t value;
|
|
int dir = (num == 80) ? REL_TO_SM : SM_TO_REL;
|
|
|
|
memset(frame, 0, sizeof(*frame));
|
|
|
|
frame->message = message[2] & 0x1f;
|
|
def = get_frame_def(frame->message, dir);
|
|
if (!def) {
|
|
PDEBUG(DFRAME, DEBUG_NOTICE, "Received unknown message type %d (maybe radio noise)\n", frame->message);
|
|
display_bits(NULL, message, num, DEBUG_NOTICE);
|
|
return -EINVAL;
|
|
}
|
|
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "Decoding frame %s %s\n", r2000_dir_name(dir), r2000_frame_name(frame->message, dir));
|
|
|
|
/* dissassemble elements elements */
|
|
value = 0;
|
|
for (i = 0; i < num; i++) {
|
|
value = (value << 1) | ((message[i / 8] >> (7 - (i & 7))) & 1);
|
|
if (def[i + 1] != def[i]) {
|
|
if (def[i] != '-') {
|
|
print_element(def[i], value, dir, DEBUG_DEBUG);
|
|
store_element(frame, def[i], value);
|
|
}
|
|
value = 0;
|
|
}
|
|
}
|
|
|
|
display_bits(def, message, num, DEBUG_DEBUG);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int assemble_frame(frame_t *frame, uint8_t *message, int num, int debug)
|
|
{
|
|
int i;
|
|
const char *def;
|
|
uint64_t value = 0; // make GCC happy
|
|
char element;
|
|
int dir = (num == 80) ? REL_TO_SM : SM_TO_REL;
|
|
|
|
def = get_frame_def(frame->message, dir);
|
|
if (!def) {
|
|
PDEBUG(DFRAME, DEBUG_ERROR, "Cannot assemble unknown message type %d, please define/fix!\n", frame->message);
|
|
abort();
|
|
}
|
|
memset(message, 0, (num + 7) / 8);
|
|
|
|
if (debug)
|
|
PDEBUG(DFRAME, DEBUG_DEBUG, "Ccoding frame %s %s\n", r2000_dir_name(dir), r2000_frame_name(frame->message, dir));
|
|
|
|
/* assemble elements elements */
|
|
element = 0;
|
|
for (i = num - 1; i >= 0; i--) {
|
|
if (element != def[i]) {
|
|
element = def[i];
|
|
switch (def[i]) {
|
|
case '-':
|
|
value = 0;
|
|
break;
|
|
case '+':
|
|
value = 0xffffffffffffffff;
|
|
break;
|
|
default:
|
|
value = fetch_element(frame, element);
|
|
}
|
|
}
|
|
message[i / 8] |= (value & 1) << (7 - (i & 7));
|
|
value >>= 1;
|
|
}
|
|
|
|
if (debug) {
|
|
for (i = 0; i < num; i++) {
|
|
if (def[i + 1] != def[i] && def[i] != '-' && def[i] != '+') {
|
|
value = fetch_element(frame, def[i]);
|
|
print_element(def[i], value, dir, DEBUG_DEBUG);
|
|
}
|
|
}
|
|
|
|
display_bits(def, message, num, DEBUG_DEBUG);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* encode frame to bits
|
|
*/
|
|
const char *encode_frame(frame_t *frame, int debug)
|
|
{
|
|
uint8_t message[11], code[23];
|
|
static char bits[32 + 176 + 1];
|
|
int i;
|
|
|
|
assemble_frame(frame, message, 80, debug);
|
|
|
|
/* hagelbarger code */
|
|
hagelbarger_encode(message, code, 88);
|
|
memcpy(bits, "10101010101010101010111100010010", 32);
|
|
for (i = 0; i < 176; i++)
|
|
bits[i + 32] = ((code[i / 8] >> (7 - (i & 7))) & 1) + '0';
|
|
bits[208] = '\0';
|
|
|
|
return bits;
|
|
}
|
|
|
|
//#define GEGENPROBE
|
|
|
|
/* decode bits to frame */
|
|
int decode_frame(frame_t *frame, const char *bits)
|
|
{
|
|
uint8_t message[11], code[23];
|
|
int i, num = strlen(bits);
|
|
|
|
#ifdef GEGENPROBE
|
|
printf("bits as received=%s\n", bits);
|
|
#endif
|
|
/* hagelbarger code */
|
|
memset(code, 0x00, sizeof(code));
|
|
for (i = 0; i < num; i++)
|
|
code[i / 8] |= (bits[i] & 1) << (7 - (i & 7));
|
|
hagelbarger_decode(code, message, num / 2 - 6);
|
|
|
|
#if 0
|
|
for (i = 0; i < num / 2; i++) {
|
|
printf("%d", (message[i / 8] >> (7 - (i & 7))) & 1);
|
|
if ((i & 7) == 7)
|
|
printf(" = 0x%02x\n", message[i / 8]);
|
|
}
|
|
#endif
|
|
|
|
#ifdef GEGENPROBE
|
|
hagelbarger_encode(message, code, num / 2);
|
|
printf("bits after re-encoding=");
|
|
for (i = 0; i < num; i++)
|
|
printf("%d", (code[i / 8] >> (7 - (i & 7))) & 1);
|
|
printf("\n");
|
|
#endif
|
|
|
|
return dissassemble_frame(frame, message, num / 2 - 8);
|
|
}
|
|
|