aboutsummaryrefslogtreecommitdiffstats
path: root/src/r2000/frame.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/r2000/frame.c')
-rw-r--r--src/r2000/frame.c573
1 files changed, 573 insertions, 0 deletions
diff --git a/src/r2000/frame.c b/src/r2000/frame.c
new file mode 100644
index 0000000..37b0a91
--- /dev/null
+++ b/src/r2000/frame.c
@@ -0,0 +1,573 @@
+/* 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 "../common/hagelbarger.h"
+#include "../common/debug.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 ID", 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--------- Supervisory----- */
+ /* messages REL->SM */
+ { REL_TO_SM, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----ccc----DDDIII++---PT---", "INSCRIPTION ACK" }, /* inscription ack */
+ { REL_TO_SM, 2, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "PLEASE WAIT" }, /* waiting on CC */
+ { REL_TO_SM, 1, "V-CCCCCCCCRRRRRRRRRMMMMM----------------------------------------DDDIII++---PT---", "IDLE" }, /* broadcast */
+ { REL_TO_SM, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN INCOMING"}, /* assign incoming call */
+ { REL_TO_SM, 4, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN GROUP"}, /* assign groupp 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, 1, "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);
+}
+