/* C-Netz telegramm 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/>.
 */

#define CHAN cnetz->sender.kanal

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "../liblogging/logging.h"
#include "cnetz.h"
#include "dsp.h"
#include "sysinfo.h"
#include "telegramm.h"

/* debug coding */
//#define DEBUG_RAW       /* debug raw bits */
//#define DEBUG_BLOCK     /* debug interleaved blocks */
//#define DEBUG_CODER	/* debug block coder / decoder */

static const char *param_ja[] = {
	"nein",
	"ja",
};

static const char *param_betriebsart[] = {
	"Sprache klar",
	"Sprache invertiert",
	"Datenbetrieb",
	"Illegaler Parameter 3",
};

static const char *param_gespraechsende[] = {
	"kein bevorstehendes Gespraechsende",
	"bevorstehendes Gespraechsende",
};

static const char *param_frequenz[2048];

static const char *param_anpassen[] = {
	"Sendeleistung erniedrigen",
	"Sendeleistung erhoehen",
};

static const char *param_opcode[64];

static const char *param_power[] = {
	"50-125 mW",
	"0.5-1 W",
	"4-8 W",
	"10-20 W",
};

static const char *param_sonderruf[] = {
	"Verbindungsaufbau gehend",
	"Sonderruf",
};

static const char *param_wskennung[] = {
	"keine Warteschlange",
	"Warteschlange",
	"Warteschlange blockiert",
	"Illegaler Parameter 3",
};

static const char *param_ausloesen[] = {
	"26 dB S/N + Pegel",
	"25 dB S/N + Pegel",
	"24 dB S/N + Pegel",
	"23 dB S/N + Pegel",
	"22 dB S/N + Pegel",
	"21 dB S/N + Pegel",
	"20 dB S/N + Pegel",
	"26 dB S/N",
	"25 dB S/N",
	"24 dB S/N",
	"23 dB S/N",
	"22 dB S/N",
	"21 dB S/N",
	"20 dB S/N",
	"19 dB S/N",
	"18 dB S/N",
};

static const char *param_ausloesegrund[] = {
	"gassenbesetzt (oder Sperre)",
	"teilnehmerbesetzt",
	"funktechnisch",
	"Illegaler Parameter 3",
};

static const char *param_genauigkeit[] = {
	"volle Genauigkeit",
	"bedingte Genauigkeit",
};

static const char *param_grenzwert[] = {
	"No limit",
	"> 15 dB S/N",
	"> 17 dB S/N",
	"> 19 dB S/N",
	"> 21 dB S/N",
	"> 25 dB S/N",
	"> 28 dB S/N",
	"> 32 dB S/N",
};

static const char *param_kennung[] = {
	"Test-FuFSt",
	"Normal-FuFSt",
	"Vorzugs-FuFSt",
	"FuFSt hoechster Prioritaet",
};

static const char *param_mittelung[] = {
	"2",
	"4",
	"8",
	"16",
	"32",
	"64",
	"Illegaler Parameter 6",
	"Illegaler Parameter 7",
	"Illegaler Parameter 8",
	"Illegaler Parameter 9",
	"Illegaler Parameter 10",
	"Illegaler Parameter 11",
	"Illegaler Parameter 12",
	"Illegaler Parameter 13",
	"Illegaler Parameter 14",
	"Illegaler Parameter 15",
};

static const char *param_entfernung[] = {
	"1,5 km",
	"2 km",
	"2,5 km",
	"3 km",
	"4 km",
	"5 km",
	"6 km",
	"7 km",
	"8 km",
	"10 km",
	"12 km",
	"14 km",
	"16 km",
	"17 km",
	"23 km",
	"30 km",
};

static const char *param_sperren[] = {
	"Ein- & Umbuchen / Gehende Verbindung",
	"Nur Ein- & Umbuchen",
	"Nur Gehende Verbindung",
	"gesperrt",
};

static const char *param_bewertung[] = {
	"Auswahl nach relativer Entfernungsbewertung",
	"Auswahl nach Pegelkreterium",
};

static const char *param_gueltig[] = {
	"gueltig",
	"ungueltig",
};

static const char *param_verkehrsanteil[] = {
	"Sonderfall",
	"1 Zeitschlitz",
	"2 Zeitschlitze",
	"3 Zeitschlitze",
	"4 Zeitschlitze",
	"5 Zeitschlitze",
	"6 Zeitschlitze",
	"7 Zeitschlitze",
	"8 Zeitschlitze",
	"9 Zeitschlitze",
	"10 Zeitschlitze",
	"11 Zeitschlitze",
	"12 Zeitschlitze",
	"13 Zeitschlitze",
	"14 Zeitschlitze",
	"15 Zeitschlitze",
	"16 Zeitschlitze",
	"17 Zeitschlitze",
	"18 Zeitschlitze",
	"19 Zeitschlitze",
	"10 Zeitschlitze",
	"21 Zeitschlitze",
	"22 Zeitschlitze",
	"23 Zeitschlitze",
	"24 Zeitschlitze",
	"25 Zeitschlitze",
	"26 Zeitschlitze",
	"27 Zeitschlitze",
	"28 Zeitschlitze",
	"29 Zeitschlitze",
	"30 Zeitschlitze",
	"31 Zeitschlitze",
};

static const char *param_signalisierung[] = {
	"Spontansignalisierung",
	"Signalisierung aus Wiederholstellung",
};

static const char *param_chipkarte[] = {
	"Magnetkarte",
	"Chipkarte",
};

static const char *param_auth[] = {
	"Authentifikation nicht durchfuehrbar",
	"Authentifikation durchfuehrbar",
};

static const char *param_reduzierung[] = {
	"4",
	"3",
	"2",
	"1",
};

static struct definition_parameter {
	char		digit;
	const char	*param_name;
	char		bits;
	const char	**value_names; /* points to a list of parameter names, NULL for integer */
} definition_parameter[] = {
	{ 'A',"FuZ-FuVSt-Nr.",				 5, NULL },
	{ 'B',"Betriebs-Art",				 2, param_betriebsart },
	{ 'C',"Ankuendigung Gespraechsende",		 1, param_gespraechsende },
	{ 'D',"Teilnehmergruppensperre",		 4, NULL },
	{ 'E',"Anzahl der gesperrten Teilnehmergruppen", 4, NULL },
	{ 'F',"FuZ-Rest-Nr.",				 8, NULL },
	{ 'G',"Gebuehren-Stand",	 		12, NULL },
	{ 'H',"OgK-Vorschlag",				10, param_frequenz },
	{ 'I',"FuZ-Nationalitaet",			 3, NULL },
	{ 'J',"Sendeleistungsanpassung",		 1, param_anpassen },
	{ 'K',"Frequenz-Nr.",				 0, param_frequenz }, /* length 10 or 11 */
	{ 'L',"Art der Signalisierung im OgK",		 1, param_signalisierung },
	{ 'M',"OgK-Verkehrsanteil",			 5, param_verkehrsanteil },
	{ 'N',"FuTln-Nationalitaet",			 3, NULL },
	{ 'O',"OP-Code der Signalisierung",		 6, param_opcode },
	{ 'P',"Max. Sendeleistung",			 2, param_power },
	{ 'Q',"Kartenkennung",	 			 3, NULL },
	{ 'R',"Durchfuehrung der Ueberlastbehandlung",	 1, param_ja },
	{ 'S',"Sonderruf",				 1, param_sonderruf },
	{ 'T',"FuTln-Rest-Nr.",				16, NULL },
	{ 'U',"FuTln-Heimmat FuVSt-Nr.",		 5, NULL },
	{ 'V',"Sicherungs-Code",			16, NULL },
	{ 'W',"WS-Kennung",				 2, param_wskennung },
	{ 'X',"Wahlziffer beliebig 16 Ziffern",		64, NULL },
	{ 'Y',"Bahn-MS",				 1, param_ja },
	{ 'Z',"Zeitschlitz-Nr.",			 5, NULL },
	{ 'a',"Grenzert fuer Ausloesen",		 4, param_ausloesen },
	{ 'b',"Chipkarten-FuTelG-Bit",			 1, param_chipkarte },
	{ 'c',"Ausloesegrund",				 2, param_ausloesegrund },
	{ 'd',"Bedingte Genauigkeit der FuFSt",		 1, param_genauigkeit },
	{ 'e',"Entfernung (generic value)",		 8, NULL },
	{ 'f',"Grenzwert fuer Einbuchen und Umbuchen",	 3, param_grenzwert },
	{ 'g',"Nachbarschafts-Prioritaets-Bit",		 1, NULL },
	{ 'h',"Herstellerkennung",			 5, NULL },
	{ 'i',"Hardwarestand des FuTelG",		 5, NULL },
	{ 'j',"Softwarestand des FuTelG",		 5, NULL },
	{ 'k',"Kennung FuFSt",				 2, param_kennung },
	{ 'l',"Authentifikationsbit",			 1, param_auth },
	{ 'm',"Mittelungs-Faktor fuer Ausloesen",	 4, param_mittelung },
	{ 'n',"Mittelungs-Faktor fuer Umschalten",	 4, param_mittelung },
	{ 'o',"Zufallszahl"			,	64, NULL },
	{ 'p',"Bewertung nach Pegel und Entfernung",	 1, param_bewertung },
	{ 'q',"Autorisierungsparameter",		64, NULL },
	{ 'r',"Entfernungsangabe der FuFSt",		 4, param_entfernung },
	{ 's',"Gueltigkeit des Gebuehrenstandes",	 1, param_gueltig },
	{ 't',"Test-Telefonteilnehmer-Geraet",		 1, param_ja },
	{ 'u',"Grenzwert fuer Umschalten",		 4, param_ausloesen },
	{ 'v',"Vermittlungtechnische Sperren",		 2, param_sperren },
	{ 'w',"Erweitertes Frequenzbandbit",		 1, NULL },
	{ 'x',"Bahn-BS",				 1, param_ja },
	{ 'y',"Reduzierungsfaktor",			 2, param_reduzierung },
	{ '_',"Illegaler Opcode",			64, NULL },
	{ 0  ,"",					 0, NULL },
};

static struct definition_parameter *get_parameter(char digit)
{
	struct definition_parameter *parameter = definition_parameter;

	for (parameter = definition_parameter; parameter->digit; parameter++) {
		if (parameter->digit == digit)
			return parameter;
	}

	return NULL;
}

static struct definition_opcode {
	const char *no_auth_bits, *auth_bits;
	const char *message_name;
	int block;
	const char *message_text;
} definition_opcode[64] = {
      /*   8888888877777777666666665555555544444444333333332222222211111111         message	block	text */
	{ "-bRLw---VVVVVVVVVVVVVVVVIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",
	  "-bRLwQQQ-hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",       "EM(R)",	BLOCK_R,"Erstmeldung" },
	{ "-bRLw---VVVVVVVVVVVVVVVVIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",
	  "-bRLwQQQ-hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",       "UM(R)",	BLOCK_R,"Umbuchantrag" },
	{ "SbRLw---VVVVVVVVVVVVVVVVIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",
	  "SbRLwQQQ-hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",       "UWG(R)",	BLOCK_R,"Umbuchantrag bei Warteschlange (gehende Verbindung)" },
	{ "--RL-----hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "ATO(R)",	BLOCK_R,"Ausloesen des FuTelG im OgK-Betrieb bei WS" },
	{ "--RL--WW-hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VWG(R)",	BLOCK_R,"Verbindungswunsch gehend" },
	{ "--RL-----hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "SRG(R)",	BLOCK_R,"Sonderruf (Notruf)" },
	{ "SbRLw---VVVVVVVVVVVVVVVVIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",
	  "SbRLwQQQ-hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",       "NUG(R)",	BLOCK_R,"Verbindungswunsch gehend bei Nachbarschaftsunterstuetzung" },
	{ "-bRLw---VVVVVVVVVVVVVVVVIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",
	  "-bRLwQQQ-hhhhhiiiiijjjjjIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT",       "UWK(R)",	BLOCK_R,"Umbuchantrag bei Warteschlange (kommende Verbindung)" },
	{ "------------------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "MFT(M)",	BLOCK_M,"Meldung: Funktelefonteilnehmer" },
	{ "________________________________________________________________", NULL, "opcode 9",	BLOCK_I,"Illegaler Opcode" },
	{ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", NULL, "WUE(M)",	BLOCK_M,"Wahluebertragung" },
	{ "________________________________________________________________", NULL, "opcode 11",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 12",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 13",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 14",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 15",BLOCK_I,"Illegaler Opcode" },
	{ "------dJ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "BEL(K)",	BLOCK_K,"Belegung" },
	{ "------dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VH(K)",	BLOCK_K,"Verbindung halten" },
	{ "------dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "RTAQ(K)",	BLOCK_K,"Quittung Rufton anschalten" },
	{ "------dJBB------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "AH(K)",	BLOCK_K,"Abhebe-Signal" },
	{ "----YwdJBBCt----eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VH(V)",	BLOCK_V,"Verbindung halten" },
	{ "------dJ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "AT(K)",	BLOCK_K,"Ausloesen durch Funktelefonteilnehmer" },
	{ "------dJBBC-------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "AT(V)",	BLOCK_V,"Ausloesen durch Funktelefonteilnehmer" },
	{ "------dJBB------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "DSQ(K)",	BLOCK_K,"Durchschalten Quittung" },
	{ "----YwdJBBCt----eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "USAI(V)",	BLOCK_V,"Umschaltantrag intern" },
	{ "----YwdJBBCt----eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "USAE(V)",	BLOCK_V,"Umschaltantrag extern" },
	{ "------dJBB--------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "USTLN(K)",	BLOCK_K,"Umschalten Funktelefonteilnehmer" },
	{ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", NULL, "ZFZQ(K)",	BLOCK_K,"Zufallszahlquittung" },
	{ "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", NULL, "AP(K)",	BLOCK_K,"Autorisierungsparameter" },
	{ "________________________________________________________________", NULL, "opcode 29",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 30",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 31",BLOCK_I,"Illegaler Opcode" },
	{ "PP-MMMMMDDDDEEEE------HHHHHHHHHHFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "MA(M)",	BLOCK_M,"Meldeaufruf" },
	{ "PPdZZZZZ-----KKKKKKKKKKKIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VAK(R)",	BLOCK_R,"Verbindungsaufbau kommend" },
	{ "________________________________________________________________", NULL, "opcode 34",BLOCK_I,"Illegaler Opcode" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "EBQ(R)",	BLOCK_R,"Einbuchquittung" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "UBQ(R)",	BLOCK_R,"Umbuchquittung" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "WSK(R)",	BLOCK_R,"Warteschglange kommend" },
	{ "PP-MMMMMDDDDEEEE------HHHHHHHHHHFFFFFFFF------------------------", NULL, "MLR(M)",	BLOCK_M,"Melde-Leer-Ruf" },
	{ "PPdZZZZZffflvvWW-----xyyIIIAAAAAFFFFFFFFkkgprrrrmmmmnnnnuuuuaaaa", NULL, "LR(R)",	BLOCK_R,"Leer-Ruf" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "ATQ(R)",	BLOCK_R,"Quittung fuer Ausloesen des FuTelG im OgK-Betrieb" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "SAR(R)",	BLOCK_R,"Sperraufruf" },
	{ "PP-MMMMMDDDDEEEE------HHHHHHHHHHFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "WAF(M)",	BLOCK_M,"Wahlaufforderung" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "WBP(R)",	BLOCK_R,"Wahlbestaetigung positiv" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "WBN(R)",	BLOCK_R,"Wahlbestaetigung negativ" },
	{ "PPdZZZZZ----------------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "WWBP(R)",	BLOCK_R,"Wahlbestaetigung positiv in Warteschlange" },
	{ "PPdZZZZZ-----KKKKKKKKKKKIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VAG(R)",	BLOCK_R,"Verbindungsaufbau gehend" },
	{ "PPdZZZZZ------cc--------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VA(R)",	BLOCK_R,"Vorzeitiges Ausloesen" },
	{ "PP----dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "BQ(K)",	BLOCK_K,"Belegungsquittung" },
	{ "PP----dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VHQ(K)",	BLOCK_K,"Quittung Verbindung halten" },
	{ "PP----dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "RTA(K)",	BLOCK_K,"Rufton anschalten" },
	{ "PP----dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "AHQ(K)",	BLOCK_K,"Abhebe-Quittierung" },
	{ "PP----dJ--C-----eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VHQ1(V)",	BLOCK_V,"Verbindung halten Quittung 1" },
	{ "PP----dJ--CsGGGGGGGGGGGGIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "VHQ2(V)",	BLOCK_V,"Verbindung halten Quittung 2" },
	{ "PP----dJ------cc--------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "AF(K)",	BLOCK_K,"Ausloesen durch FuFSt in konzentr. Signalisierung" },
	{ "PP----dJ------cc--------IIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "AF(V)",	BLOCK_V,"Ausloesen durch FuFSt in verteilter Signalisierung" },
	{ "PP----dJ--------eeeeeeeeIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "DSB(K)",	BLOCK_K,"Durchschaltung" },
	{ "PP----dJ-----KKKKKKKKKKKIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "DSBI(V)",	BLOCK_V,"Umschaltbefehl intern (neuer SpK in der gleichen FuZ)" },
	{ "PPuuuudJ-xnnnnKKKKKKKKKKIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "USF(K)",	BLOCK_K,"Umschalten FuFst" },
	{ "PP----dJ-----KKKKKKKKKKKIIIAAAAAFFFFFFFFNNNUUUUUTTTTTTTTTTTTTTTT", NULL, "USBE(V)",	BLOCK_V,"Umschaltbefehl extern (neuer SpK in einer anderen Funkzelle)" },
	{ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", NULL, "ZFZ(K)",	BLOCK_K,"Zufallszahl" },
	{ "________________________________________________________________", NULL, "opcode 61",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 62",BLOCK_I,"Illegaler Opcode" },
	{ "________________________________________________________________", NULL, "opcode 63",BLOCK_I,"Illegaler Opcode" },
};

const char *telegramm_name(uint8_t opcode)
{
	return definition_opcode[opcode].message_name;
}

int init_telegramm(void)
{
	struct definition_parameter *parameter;
	int i, j;
	const char *bits;
	char last_bit;
	int count_bits;

	/* copy no_auth_bits to auth_bits, if required
	 * check if the number of bits in a message matches the number of bits of a parameter */
	for (i = 0; i < 64; i++) {
		if (definition_opcode[i].auth_bits == NULL)
			definition_opcode[i].auth_bits = definition_opcode[i].no_auth_bits;
		for (bits = definition_opcode[i].no_auth_bits; ; bits = definition_opcode[i].auth_bits) {
			last_bit = '-';
			count_bits = 0;
			for (j = 0; j < 65; j++) { /* include termination character */
				if (last_bit != bits[j]) {
					if (last_bit != '-') {
						parameter = get_parameter(last_bit);
						if (!parameter) {
							printf("Message #%d has invalid digit '%c'\n", i, last_bit);
							return -1;
						}
						if (parameter->bits && parameter->bits != count_bits) {
							printf("Message #%d has digit '%c' with %d bits, but parameter has %d bits\n", i, last_bit, count_bits, parameter->bits);
							return -1;
						}
					}
					last_bit = bits[j];
					count_bits = 0;
				}
				count_bits++;
			}
			if (bits == definition_opcode[i].auth_bits)
				break;
		}
	}

	/* generate frequency names */
	for (i = 0; i < 2048; i++) {
		char *frequenz = calloc(16, 1);
		if ((i & 1))
			sprintf(frequenz, "%.4f MHz", 465.750 - (double)(i+1) / 2.0 * 0.010);
		else
			sprintf(frequenz, "%.4f MHz", 465.750 - (double)i / 2.0 * 0.0125);
		param_frequenz[i] = frequenz;
	}

	/* generate opcode names */
	for (i = 0; i < 64; i++)
		param_opcode[i] = definition_opcode[i].message_name;

	return 0;
}

const char *telegramm2rufnummer(telegramm_t *telegramm)
{
	static char rufnummer[32]; /* make GCC happy (overflow check) */

	sprintf(rufnummer, "%d%d%05d", telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr);

	return rufnummer;
}

static void decode_dialstring(char *number, uint64_t value)
{
	int index, max, shift;
	int digit;

	if ((value & 0x000000000000000f) == 0) {
		max = 16;
		index = 1;
		number[0] = '0';
	} else {
		max = value & 0x000000000000000f;
		index = 0;
	}
	shift = 4;
	while (index < max) {
		digit = (value >> shift) & 0xf;
		switch (digit) {
		case 0xb:
			digit = '*';
			break;
		case 0xc:
			digit = '#';
			break;
		case 0xa:
		case 0xd:
		case 0xe:
		case 0xf:
			digit = digit - 0xa + 'a';
			break;
		default:
			digit = digit + '0';
		}
		number[index] = digit;
		index++;
		shift += 4;
	}
	number[index] = '\0';
}

static int encode_dialstring(uint64_t *value, const char *number)
{
	int max, index, shift, digit;

	max = strlen(number);
	if (max > 16) {
		LOGP(DFRAME, LOGL_NOTICE, "Given number '%s' has more than 16 digits\n", number);
		return -EINVAL;
	}

	if (max == 16) {
		if (number[0] != '0') {
			LOGP(DFRAME, LOGL_NOTICE, "Given 16 digit number '%s' does not start with '0'\n", number);
			return -EINVAL;
		}
		*value = 0;
		index = 1;
	} else {
		*value = strlen(number);
		index = 0;
	}
	shift = 4;
	while (index < max) {
		digit = number[index];
		switch (digit) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			digit = digit - '0';
			break;
		case 'a':
		case 'd':
		case 'e':
		case 'f':
			digit = digit - 'a' + 0xa;
			break;
		case 'A':
		case 'D':
		case 'E':
		case 'F':
			digit = digit - 'A' + 0xa;
			break;
		case '*':
			digit = 0xb;
			break;
		case '#':
			digit = 0xc;
			break;
		default:
			return -EINVAL;
		}
		*value |= (uint64_t)digit << shift;
		index++;
		shift += 4;
	}

	return 0;
}

int match_fuz(telegramm_t *telegramm)
{
	if (telegramm->fuz_nationalitaet != si.fuz_nat
	 || telegramm->fuz_fuvst_nr != si.fuz_fuvst
	 || telegramm->fuz_rest_nr != si.fuz_rest) {
		LOGP(DFRAME, LOGL_NOTICE, "Ignoring message from mobile phone %d,%d,%d: Cell 'Funkzelle' does not match!\n", telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr);
	 	return 0;
	}

	return 1;
}

int match_futln(telegramm_t *telegramm, uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest)
{
	if (telegramm->futln_nationalitaet != futln_nat
	 || telegramm->futln_heimat_fuvst_nr != futln_fuvst
	 || telegramm->futln_rest_nr != futln_rest) {
		LOGP(DFRAME, LOGL_NOTICE, "Ignoring message from mobile phone %d,%d,%d: Mobile station 'Funktelefongeraet' does not match!\n", telegramm->futln_nationalitaet, telegramm->futln_heimat_fuvst_nr, telegramm->futln_rest_nr);
	 	return 0;
	}

	return 1;
}

static void debug_parameter(char digit, uint64_t value)
{
	struct definition_parameter *parameter;

	parameter = get_parameter(digit);
	if (!parameter) {
		LOGP(DFRAME, LOGL_ERROR, "Digit '%c' not found in definition_parameter list, please fix!\n", digit);
		abort();
	}
	if (parameter->value_names)
		LOGP(DFRAME, LOGL_DEBUG, " (%c) %s : %s\n", digit, parameter->param_name, parameter->value_names[value]);
	else if (parameter->bits == 64)
		LOGP(DFRAME, LOGL_DEBUG, " (%c) %s : 0x%016" PRIx64 "\n", digit, parameter->param_name, value);
	else if (digit == 'X') {
		char wahlziffern[17];
		decode_dialstring(wahlziffern, value);
		LOGP(DFRAME, LOGL_DEBUG, " (%c) %s : '%s'\n", digit, parameter->param_name, wahlziffern);
	} else
		LOGP(DFRAME, LOGL_DEBUG, " (%c) %s : %" PRIu64 "\n", digit, parameter->param_name, value);
}

/* encode telegram to 70 bits
 * bit order MSB
 */
static char *assemble_telegramm(const telegramm_t *telegramm, int debug)
{
	static char bits[71]; /* + termination char for debug */
	char parameter;
	const char *string;
	uint64_t value, val;
	int i, j;
	int rc;

	if (telegramm->opcode >= 64) {
		LOGP(DFRAME, LOGL_ERROR, "Opcode '0x%x' exceeds bit range, please fix!\n", telegramm->opcode);
		abort();
	}

	if (debug)
		LOGP(DFRAME, LOGL_INFO, "Coding %s %s\n", definition_opcode[telegramm->opcode].message_name, definition_opcode[telegramm->opcode].message_text);

	/* copy opcode */
	for (i = 0; i < 6; i++)
		bits[5 - i] = (telegramm->opcode & (1 << i)) ? '1' : '0';

	/* copy parameters */
	string = definition_opcode[telegramm->opcode].no_auth_bits;
	for (i = 0; i < 64; i++) {
		parameter = string[63 - i];
		if (parameter == '-') {
			bits[69 - i] = '0';
			continue;
		}
		switch (parameter) {
		case 'A':
			value = telegramm->fuz_fuvst_nr;
			break;
		case 'B':
			value = telegramm->betriebs_art;
			break;
		case 'C':
			value = telegramm->ankuendigung_gespraechsende;
			break;
		case 'D':
			value = telegramm->teilnehmergruppensperre;
			break;
		case 'E':
			value = telegramm->anzahl_gesperrter_teilnehmergruppen;
			break;
		case 'F':
			value = telegramm->fuz_rest_nr;
			break;
		case 'G':
			value = telegramm->gebuehren_stand;
			break;
		case 'H':
			value = telegramm->ogk_vorschlag;
			break;
		case 'I':
			value = telegramm->fuz_nationalitaet;
			break;
		case 'J':
			value = telegramm->sendeleistungsanpassung;
			break;
		case 'K':
			value = telegramm->frequenz_nr;
			break;
		case 'L':
			value = telegramm->art_der_signalisierung_im_ogk;
			break;
		case 'M':
			value = telegramm->ogk_verkehrsanteil;
			break;
		case 'N':
			value = telegramm->futln_nationalitaet;
			break;
		case 'P':
			value = telegramm->max_sendeleistung;
			break;
		case 'Q':
			value = telegramm->kartenkennung;
			break;
		case 'R':
			value = telegramm->durchfuehrung_der_ueberlastbehandlung;
			break;
		case 'S':
			value = telegramm->sonderruf;
			break;
		case 'T':
			value = telegramm->futln_rest_nr;
			break;
		case 'U':
			value = telegramm->futln_heimat_fuvst_nr;
			break;
		case 'V':
			value = telegramm->sicherungs_code;
			break;
		case 'W':
			value = telegramm->ws_kennung;
			break;
		case 'X':
			rc = encode_dialstring(&value, telegramm->wahlziffern);
			if (rc < 0) {
				LOGP(DFRAME, LOGL_ERROR, "Illegal dial string '%s', please fix!\n", telegramm->wahlziffern);
				abort();
			}
			break;
		case 'Y':
			value = telegramm->bahn_ms;
			break;
		case 'Z':
			value = telegramm->zeitschlitz_nr;
			break;
		case 'a':
			value = telegramm->grenze_fuer_ausloesen;
			break;
		case 'b':
			value = telegramm->chipkarten_futelg_bit;
			break;
		case 'c':
			value = telegramm->ausloesegrund;
			break;
		case 'd':
			value = telegramm->bedingte_genauigkeit_der_fufst;
			break;
		case 'e':
			value = telegramm->entfernung;
			break;
		case 'f':
			value = telegramm->grenzwert_fuer_einbuchen_und_umbuchen;
			break;
		case 'g':
			value = telegramm->nachbarschafts_prioritaets_bit;
			break;
		case 'h':
			value = telegramm->herstellerkennung;
			break;
		case 'i':
			value = telegramm->hardware_des_futelg;
			break;
		case 'j':
			value = telegramm->software_des_futelg;
			break;
		case 'k':
			value = telegramm->kennung_fufst;
			break;
		case 'l':
			value = telegramm->authentifikationsbit;
			break;
		case 'm':
			value = telegramm->mittelungsfaktor_fuer_ausloesen;
			break;
		case 'n':
			value = telegramm->mittelungsfaktor_fuer_umschalten;
			break;
		case 'o':
			value = telegramm->zufallszahl;
			break;
		case 'p':
			value = telegramm->bewertung_nach_pegel_und_entfernung;
			break;
		case 'q':
			value = telegramm->authorisierungsparameter;
			break;
		case 'r':
			value = telegramm->entfernungsangabe_der_fufst;
			break;
		case 's':
			value = telegramm->gueltigkeit_des_gebuehrenstandes;
			break;
		case 't':
			value = telegramm->test_telefonteilnehmer_geraet;
			break;
		case 'u':
			value = telegramm->grenzwert_fuer_umschalten;
			break;
		case 'v':
			value = telegramm->vermittlungstechnische_sperren;
			break;
		case 'w':
			value = telegramm->erweitertes_frequenzbandbit;
			break;
		case 'x':
			value = telegramm->bahn_bs;
			break;
		case 'y':
			value = telegramm->reduzierungsfaktor;
			break;
		case '_':
			value = telegramm->illegaler_opcode;
			break;
		default:
			LOGP(DFRAME, LOGL_ERROR, "Parameter '%c' does not exist, please fix!\n", parameter);
			abort();
		}
		if (debug && loglevel <= LOGL_DEBUG)
			debug_parameter(parameter, value);
		val = value;
		for (j = 0; string[63 - i - j] == parameter; j++) {
			bits[69 - i - j] = (val & 1) ? '1' : '0';
			val >>= 1;
		}
		if (val)
			LOGP(DFRAME, LOGL_ERROR, "Parameter '%c' value '0x%" PRIx64 "' exceeds bit range!\n", parameter, value);
		i += j - 1;
	}
	bits[70] = '\0';

	if (debug) {
		LOGP(DFRAME, LOGL_DEBUG, "OOOOOO%s\n", string);
		LOGP(DFRAME, LOGL_DEBUG, "%s\n", bits);
	}

	return bits;
}

/* decode telegram from 70 bits
 * bit order MSB
 */
static void disassemble_telegramm(telegramm_t *telegramm, const char *bits, int auth)
{
	uint64_t value;
	const char *string;
	char parameter;
	int i, j;

	memset(telegramm, 0, sizeof(*telegramm));

	/* copy opcode */
	value = 0;
	for (i = 0; i < 6; i++)
		value = (value << 1) | (bits[i] == '1');
	telegramm->opcode = value;

	LOGP(DFRAME, LOGL_INFO, "Decoding %s %s\n", definition_opcode[telegramm->opcode].message_name, definition_opcode[telegramm->opcode].message_text);

	/* copy parameters */
	if (auth && bits[1]) /* auth flag and chip card flag */
		string = definition_opcode[telegramm->opcode].auth_bits;
	else
		string = definition_opcode[telegramm->opcode].no_auth_bits;
	for (i = 0; i < 64; i++) {
		parameter = string[63 - i];
		if (parameter == '-')
			continue;
		value = 0;
		for (j = 0; i + j < 64 && string[63 - i - j] == parameter; j++)
			value = (value >> 1) | ((uint64_t)(bits[69 - i - j] == '1') << 63);
		value >>= 64 - j;
		i += j - 1;
		if (loglevel <= LOGL_DEBUG)
			debug_parameter(parameter, value);
		switch (parameter) {
		case 'A':
			telegramm->fuz_fuvst_nr = value;
			break;
		case 'B':
			telegramm->betriebs_art = value;
			break;
		case 'C':
			telegramm->ankuendigung_gespraechsende = value;
			break;
		case 'D':
			telegramm->teilnehmergruppensperre = value;
			break;
		case 'E':
			telegramm->anzahl_gesperrter_teilnehmergruppen = value;
			break;
		case 'F':
			telegramm->fuz_rest_nr = value;
			break;
		case 'G':
			telegramm->gebuehren_stand = value;
			break;
		case 'H':
			telegramm->ogk_vorschlag = value;
			break;
		case 'I':
			telegramm->fuz_nationalitaet = value;
			break;
		case 'J':
			telegramm->sendeleistungsanpassung = value;
			break;
		case 'K':
			telegramm->frequenz_nr = value;
			break;
		case 'L':
			telegramm->art_der_signalisierung_im_ogk = value;
			break;
		case 'M':
			telegramm->ogk_verkehrsanteil = value;
			break;
		case 'N':
			telegramm->futln_nationalitaet = value;
			break;
		case 'P':
			telegramm->max_sendeleistung = value;
			break;
		case 'Q':
			telegramm->kartenkennung = value;
			break;
		case 'R':
			telegramm->durchfuehrung_der_ueberlastbehandlung = value;
			break;
		case 'S':
			telegramm->sonderruf = value;
			break;
		case 'T':
			telegramm->futln_rest_nr = value;
			break;
		case 'U':
			telegramm->futln_heimat_fuvst_nr = value;
			break;
		case 'V':
			telegramm->sicherungs_code = value;
			break;
		case 'W':
			telegramm->ws_kennung = value;
			break;
		case 'X':
			decode_dialstring(telegramm->wahlziffern, value);
			break;
		case 'Y':
			telegramm->bahn_ms = value;
			break;
		case 'Z':
			telegramm->zeitschlitz_nr = value;
			break;
		case 'a':
			telegramm->grenze_fuer_ausloesen = value;
			break;
		case 'b':
			telegramm->chipkarten_futelg_bit = value;
			break;
		case 'c':
			telegramm->ausloesegrund = value;
			break;
		case 'd':
			telegramm->bedingte_genauigkeit_der_fufst = value;
			break;
		case 'e':
			telegramm->entfernung = value;
			break;
		case 'f':
			telegramm->grenzwert_fuer_einbuchen_und_umbuchen = value;
			break;
		case 'g':
			telegramm->nachbarschafts_prioritaets_bit = value;
			break;
		case 'h':
			telegramm->herstellerkennung = value;
			break;
		case 'i':
			telegramm->hardware_des_futelg = value;
			break;
		case 'j':
			telegramm->software_des_futelg = value;
			break;
		case 'k':
			telegramm->kennung_fufst = value;
			break;
		case 'l':
			telegramm->authentifikationsbit = value;
			break;
		case 'm':
			telegramm->mittelungsfaktor_fuer_ausloesen = value;
			break;
		case 'n':
			telegramm->mittelungsfaktor_fuer_umschalten = value;
			break;
		case 'o':
			telegramm->zufallszahl = value;
			break;
		case 'p':
			telegramm->bewertung_nach_pegel_und_entfernung = value;
			break;
		case 'q':
			telegramm->authorisierungsparameter = value;
			break;
		case 'r':
			telegramm->entfernungsangabe_der_fufst = value;
			break;
		case 's':
			telegramm->gueltigkeit_des_gebuehrenstandes = value;
			break;
		case 't':
			telegramm->test_telefonteilnehmer_geraet = value;
			break;
		case 'u':
			telegramm->grenzwert_fuer_umschalten = value;
			break;
		case 'v':
			telegramm->vermittlungstechnische_sperren = value;
			break;
		case 'w':
			telegramm->erweitertes_frequenzbandbit = value;
			break;
		case 'x':
			telegramm->bahn_bs = value;
			break;
		case 'y':
			telegramm->reduzierungsfaktor = value;
			break;
		case '_':
			telegramm->illegaler_opcode = value;
			break;
		default:
			LOGP(DFRAME, LOGL_ERROR, "Parameter '%c' does not exist, please fix!\n", parameter);
			abort();
		}
	}

	if (loglevel <= LOGL_DEBUG) {
		char debug_bits[71];

		memcpy(debug_bits, bits, 70);
		debug_bits[70] = '\0';
		LOGP(DFRAME, LOGL_DEBUG, "OOOOOO%s\n", string);
		LOGP(DFRAME, LOGL_DEBUG, "%s\n", debug_bits);
	}

}

static const char *barker_string = "11100010010";
static int16_t barker_code = 0x712; /* 11 bits: 11100010010 */
static uint8_t barker_decode[2048]; /* detected bits */

static char *blockcode[128] = {
/*	 0123456 = Nutzbits */
/*	           01234567 = Redundanzbits */
	"0000000" "00000000",
	"1000000" "11101000",
	"0100000" "01110100",
	"1100000" "10011100",
	"0010000" "00111010",
	"1010000" "11010010",
	"0110000" "01001110",
	"1110000" "10100110",
	"0001000" "00011101",
	"1001000" "11110101",
	"0101000" "01101001",
	"1101000" "10000001",
	"0011000" "00100111",
	"1011000" "11001111",
	"0111000" "01010011",
	"1111000" "10111011",
	"0000100" "11100110",
	"1000100" "00001110",
	"0100100" "10010010",
	"1100100" "01111010",
	"0010100" "11011100",
	"1010100" "00110100",
	"0110100" "10101000",
	"1110100" "01000000",
	"0001100" "11111011",
	"1001100" "00010011",
	"0101100" "10001111",
	"1101100" "01100111",
	"0011100" "11000001",
	"1011100" "00101001",
	"0111100" "10110101",
	"1111100" "01011101",
	"0000010" "01110011",
	"1000010" "10011011",
	"0100010" "00000111",
	"1100010" "11101111",
	"0010010" "01001001",
	"1010010" "10100001",
	"0110010" "00111101",
	"1110010" "11010101",
	"0001010" "01101110",
	"1001010" "10000110",
	"0101010" "00011010",
	"1101010" "11110010",
	"0011010" "01010100",
	"1011010" "10111100",
	"0111010" "00100000",
	"1111010" "11001000",
	"0000110" "10010101",
	"1000110" "01111101",
	"0100110" "11100001",
	"1100110" "00001001",
	"0010110" "10101111",
	"1010110" "01000111",
	"0110110" "11011011",
	"1110110" "00110011",
	"0001110" "10001000",
	"1001110" "01100000",
	"0101110" "11111100",
	"1101110" "00010100",
	"0011110" "10110010",
	"1011110" "01011010",
	"0111110" "11000110",
	"1111110" "00101110",
	"0000001" "11010001",
	"1000001" "00111001",
	"0100001" "10100101",
	"1100001" "01001101",
	"0010001" "11101011",
	"1010001" "00000011",
	"0110001" "10011111",
	"1110001" "01110111",
	"0001001" "11001100",
	"1001001" "00100100",
	"0101001" "10111000",
	"1101001" "01010000",
	"0011001" "11110110",
	"1011001" "00011110",
	"0111001" "10000010",
	"1111001" "01101010",
	"0000101" "00110111",
	"1000101" "11011111",
	"0100101" "01000011",
	"1100101" "10101011",
	"0010101" "00001101",
	"1010101" "11100101",
	"0110101" "01111001",
	"1110101" "10010001",
	"0001101" "00101010",
	"1001101" "11000010",
	"0101101" "01011110",
	"1101101" "10110110",
	"0011101" "00010000",
	"1011101" "11111000",
	"0111101" "01100100",
	"1111101" "10001100",
	"0000011" "10100010",
	"1000011" "01001010",
	"0100011" "11010110",
	"1100011" "00111110",
	"0010011" "10011000",
	"1010011" "01110000",
	"0110011" "11101100",
	"1110011" "00000100",
	"0001011" "10111111",
	"1001011" "01010111",
	"0101011" "11001011",
	"1101011" "00100011",
	"0011011" "10000101",
	"1011011" "01101101",
	"0111011" "11110001",
	"1111011" "00011001",
	"0000111" "01000100",
	"1000111" "10101100",
	"0100111" "00110000",
	"1100111" "11011000",
	"0010111" "01111110",
	"1010111" "10010110",
	"0110111" "00001010",
	"1110111" "11100010",
	"0001111" "01011001",
	"1001111" "10110001",
	"0101111" "00101101",
	"1101111" "11000101",
	"0011111" "01100011",
	"1011111" "10001011",
	"0111111" "00010111",
	"1111111" "11111111",
};

static uint16_t block_code[128];
static uint16_t block_decode[32768]; /* code word + flag / 0xffff=decode error */

int init_coding(void)
{
	int i, j, k;

	/* create table to decode barker code.
	 * ech table entry returns the number of detected bits */
	for (i = 0; i < 2048; i++) {
		int match = 0;
		for (j = 0; j < 11; j++) {
			/* check if i matches barker code at given bit j */
			if (((i ^ barker_code) & (0x400 >> j)) == 0)
				match++;
		}
		barker_decode[i] = match;
	}

	/* convert string to block code words */
	for (i = 0; i < 128; i++) {
		int word = 0;
		for (j = 0; j < 15; j++)
			word = (word << 1) + (blockcode[i][14 - j] - '0');
		if ((word & 0x7f) != i) {
			printf("Databits are wrong, expecting %d, but got %d\n", i, word & 0x7f);
			return -1;
		}
		block_code[i] = word;
	}

	/* check if redundancy of a single bit matches the combined redundancy */
	for (i = 0; i < 128; i++) {
		int r = 0;
		for (j = 0; j < 7; j++) {
			if ((i & (1 << j)))
				r ^= block_code[1 << j] >> 7;
		}
		if (r != block_code[i] >> 7) {
			printf("Redundancy bits are wrong\n");
			return -1;
		}
	}

	/* create table to decode one block code and return value + error */
	/* set all combinations invalid */
	for (i = 0; i < 32768; i++)
		block_decode[i] = 0xffff;
	for (i = 0; i < 128; i++) {
		int word;
		/* set all error free combinations valid */
		word = block_code[i];
		if (block_decode[word] != 0xffff) {
			printf("Overlap, please fix!\n");
			return -1;
		}
		block_decode[word] = i;
		/* set all one bit error combinations valid with flag */
		for (j = 0; j < 15; j++) {
			word = block_code[i];
			word ^= (1 << j);
			if (block_decode[word] != 0xffff) {
				printf("Overlap, please fix!\n");
				return -1;
			}
			block_decode[word] = i | 0x100; /* indicate 1 error */
			/* set all two bit error combinations valid with flag */
			for (k = j + 1; k < 15; k++) {
				word = block_code[i];
				word ^= (1 << j) | (1 << k);
				if (block_decode[word] != 0xffff) {
					printf("Overlap, please fix!\n");
					return -1;
				}
				block_decode[word] = i | 0x200; /* indicate 2 errors */
			}
		}
	}

#if 0
	int count = 0;
	for (i = 0; i < 32768; i++) {
		printf("%d,", (int16_t)block_decode[i]);
		if (block_decode[i] == 0xffff)
		count++;
	}
	printf("bad blocks = %d\n", count);
#endif

	return 0;
}

/* check for sync (3 * barker code) + 1 bit */
int detect_sync(uint64_t bitstream)
{
	int match;

	/* hack: ignore first 3 bits of first barker code */
	bitstream |= 0x380000000;

	/* metch 33 bits, not as specified by FTZ */
	match = barker_decode[(bitstream >> 23) & 0x7ff];
	if (match < 11)
		return 0;
	match += barker_decode[(bitstream >> 12) & 0x7ff];
	if (match < 22)
		return 0;
	match += barker_decode[(bitstream >> 1) & 0x7ff];
	if (match < 33)
		return 0;

	return 1;
}

/* encode data block
 * input: 70 data bits MSB first
 * output: 10*15 code words (LSB first)
 * FTZ 171 TR 60 / 5.1.1.3 */
static char *encode(const char *input)
{
	static char output[150];
	int16_t word;
	int i, j;

#ifdef DEBUG_CODER
	printf("Encoding block to transmit:\n");
	printf("0123456.01234567\n");
#endif
	for (i = 0; i < 10; i++) {
		word = 0;
		for (j = 0; j < 7; j++)
			word = (word << 1) | (input[(9 - i) * 7 + j] == '1');
		word = block_code[word];
		for (j = 0; j < 15; j++) {
			output[i * 15 + j] = ((word >> j) & 1) + '0';
#ifdef DEBUG_CODER
			printf("%c", output[i * 15 + j]);
			if (j == 6)
				printf(".");
#endif
		}
#ifdef DEBUG_CODER
		printf("\n");
#endif
	}

	return output;
}

/* decode data block
 * input: 10*15 code words (LSB first)
 * output: 70 data bits MSB first
 * FTZ 171 TR 60 / 5.1.1.3 */
static const char *decode(const char *input, int *_bit_errors)
{
	int failed = 0, warn = 0;
	char fail_str[11];
	static char output[70];
	uint16_t word;
	int i, j;

#ifdef DEBUG_CODER
	printf("Decoding received block:\n");
	printf("0123456.01234567 Without errors:  Error bits:\n");
#endif
	for (i = 0; i < 10; i++) {
		word = 0;
		for (j = 0; j < 15; j++) {
			word = (word << 1) | (input[i * 15 + 14 - j] == '1');
#ifdef DEBUG_CODER
			printf("%c", input[i * 15 + j]);
			if (j == 6)
				printf(".");
#endif
		}
		word = block_decode[word];
		for (j = 0; j < 7; j++) {
			output[(9 - i) * 7 + j] = ((word >> (6 - j)) & 1) + '0';
		}
		if (word > 0x2ff) {
			failed = 1;
			fail_str[i] = 'X';
		} else
		if (word > 0x1ff) {
			warn += 2;
			fail_str[i] = '2';
		}else
		if (word > 0xff) {
			warn += 1;
			fail_str[i] = '1';
		} else
			fail_str[i] = '.';
#ifdef DEBUG_CODER
		if (word > 0x2ff)
			printf("decode failed");
		else {
			printf(" ");
			for (j = 0; j < 15; j++) {
				printf("%c", blockcode[word & 0x7f][j]);
				if (j == 6)
					printf(".");
			}
			printf(" ");
			for (j = 0; j < 15; j++) {
				if (blockcode[word & 0x7f][j] != input[i * 15 + j])
					printf("*");
				else
					printf("-");
				if (j == 6)
					printf(".");
			}
		}
		printf("\n");
#endif
	}
	fail_str[10] = '\0';
	
	if (failed)
		LOGP(DFRAME, LOGL_DEBUG, "Received Telegram with these block errors: '%s' (X = uncorrectable)\n", fail_str);
	else if (warn)
		LOGP(DFRAME, LOGL_DEBUG, "Received Telegram with these block errors: '%s' (1 / 2 = correctable)\n", fail_str);
	else
		LOGP(DFRAME, LOGL_DEBUG, "Received Telegram with no block errors.\n");

	if (failed)
		return NULL;
	*_bit_errors = warn;
	return output;
}

/* interleving of code words
 * input: 10*15 code words (LSB first)
 * output: stream of 33 sync + 1 + 150 interleaved bits
 * FTZ 171 TR 60 / 5.1.1.2 and 5.1.1.2 */
static char *interleave(const char *input)
{
	static char output[185]; /* + termination char for debug */
	int i, j;

	strcpy(output, barker_string);
	strcpy(output + 11, barker_string);
	strcpy(output + 22, barker_string);

	output[33] = '1';

#ifdef DEBUG_BLOCK
	printf("Interleaving block to transmit:\n");
#endif
	for (i = 0; i < 10; i++) {
		for (j = 0; j < 15; j++) {
			output[i + j * 10 + 34] = input[j + i * 15];
#ifdef DEBUG_BLOCK
			printf("%c", input[j + i * 15]);
#endif
		}
#ifdef DEBUG_BLOCK
		printf("\n");
#endif
	}

#ifdef DEBUG_RAW
	output[184] = '\0';
	printf("Raw TX: %s\n", output + 34);
#endif

	return output;
}

/* deinterleave of code words
 * input: stream of 150 interleaved bits
 * output: 10*15 code words (LSB first)
 * FTZ 171 TR 60 / 5.1.1.4 */
static const char *deinterleave(const char *input)
{
	static char output[150];
	int i, j;

#ifdef DEBUG_RAW
	char debug_bits[151];

	memcpy(debug_bits, input, 150);
	debug_bits[151] = '\0';
	printf("Raw RX: %s\n", debug_bits);
#endif

#ifdef DEBUG_BLOCK
	printf("Deinterleaving received block:\n");
#endif
	for (i = 0; i < 10; i++) {
		for (j = 0; j < 15; j++) {
			output[j + i * 15] = input[i + j * 10];
#ifdef DEBUG_BLOCK
			printf("%c", output[j + i * 15]);
#endif
		}
#ifdef DEBUG_BLOCK
		printf("\n");
#endif
	}

	return output;
}

void cnetz_decode_telegramm(cnetz_t *cnetz, const char *bits, double level, double sync_time, double stddev)
{
	telegramm_t telegramm;
	uint8_t opcode;
	int i;
	int block;
	int bit_errors;

	bits = deinterleave(bits);
	bits = decode(bits, &bit_errors);
	if (!bits)
		return;

	/* filter out mysterious zero-telegramm */
	for (i = 0; i < 70; i++) {
		if (bits[i] != bits[0])
			break;
	}
	if (i == 70) {
		LOGP(DFRAME, LOGL_INFO, "Ignoring mysterious unmodulated telegramm (noise from phone's transmitter)\n");
		return;
	}

	LOGP_CHAN(DDSP, LOGL_INFO, "RF level: %.1f dB RX Level: %.0f%% Standard deviation: %.0f%% Sync Time: %.2f (TS %.2f) %s\n", cnetz->rf_level_db, fabs(level) / cnetz->fsk_deviation * 100.0, stddev / fabs(level) * 100.0, sync_time, sync_time / 396.0, (level < 0) ? "NEGATIVE (phone's mode)" : "POSITIVE (base station's mode)");
	if (bit_errors)
		LOGP_CHAN(DDSP, LOGL_INFO, " -> Frame has %d bit errors.\n", bit_errors);

	disassemble_telegramm(&telegramm, bits, si.authentifikationsbit);
	opcode = telegramm.opcode;
	telegramm.level = level;
	telegramm.sync_time = sync_time;

	if (cnetz->sender.loopback) {
		LOGP(DFRAME, LOGL_NOTICE, "Received Telegramm in loopback test mode (opcode %d = %s)\n", opcode, definition_opcode[opcode].message_name);
		cnetz_sync_frame(cnetz, sync_time, -1);
		return;
	}

	if (opcode >= 32) {
		LOGP(DFRAME, LOGL_NOTICE, "Received Telegramm that is not used by mobile station, ignoring! (opcode %d = %s)\n", opcode, definition_opcode[opcode].message_name);
		return;
	}

	if (definition_opcode[opcode].block == BLOCK_I) {
		LOGP(DFRAME, LOGL_NOTICE, "Received Telegramm that is an illegal opcode, ignoring! (opcode %d = %s)\n", opcode, definition_opcode[opcode].message_name);
		return;
	}

	/* auto select cell */
	if (cnetz->auto_polarity && match_fuz(&telegramm)) {
		sender_t *sender;
		cnetz_t *c;

		printf("***********************************************\n");
		printf("*** Autoselecting %stive FSK TX polarity! ***\n", (cnetz->negative_polarity) ? "nega" : "posi");
		printf("***********************************************\n");
		/* select on all transceivers */
		for (sender = sender_head; sender; sender = sender->next) {
			c = (cnetz_t *) sender;
			c->auto_polarity = 0;
			c->negative_polarity = cnetz->negative_polarity;
		}
	}

	switch (cnetz->dsp_mode) {
	case DSP_MODE_OGK:
		if (definition_opcode[opcode].block != BLOCK_R && definition_opcode[opcode].block != BLOCK_M) {
			LOGP(DFRAME, LOGL_NOTICE, "Received Telegramm that is not used OgK channel signaling, ignoring! (opcode %d = %s)\n", opcode, definition_opcode[opcode].message_name);
			return;
		}
		/* determine block by last timeslot sent and by message type
		 * this is needed to sync the time of the receiver
		 */
		block = cnetz->sched_last_ts * 2;
		if (definition_opcode[opcode].block == BLOCK_M)
			block++;
		cnetz_receive_telegramm_ogk(cnetz, &telegramm, block);
		break;
	case DSP_MODE_SPK_K:
		if (definition_opcode[opcode].block != BLOCK_K) {
			LOGP(DFRAME, LOGL_NOTICE, "Received Telegramm that is not used for concentrated signaling, ignoring! (opcode %d = %s)\n", opcode, definition_opcode[opcode].message_name);
			return;
		}
		cnetz_receive_telegramm_spk_k(cnetz, &telegramm);
		break;
	case DSP_MODE_SPK_V:
		if (definition_opcode[opcode].block != BLOCK_V) {
			LOGP(DFRAME, LOGL_NOTICE, "Received Telegramm that is not used for distributed signaling, ignoring! (opcode %d = %s)\n", opcode, definition_opcode[opcode].message_name);
			return;
		}
		cnetz_receive_telegramm_spk_v(cnetz, &telegramm);
		break;
	default:
		;
	}
}

const char *cnetz_encode_telegramm(cnetz_t *cnetz)
{
	const telegramm_t *telegramm = NULL;
	uint8_t opcode;
	char *bits;
	int debug = 1;

	switch (cnetz->dsp_mode) {
	case DSP_MODE_OGK:
		if (!cnetz->sched_r_m)
			telegramm = cnetz_transmit_telegramm_rufblock(cnetz);
		else
			telegramm = cnetz_transmit_telegramm_meldeblock(cnetz);
		break;
	case DSP_MODE_SPK_K:
		telegramm = cnetz_transmit_telegramm_spk_k(cnetz);
		break;
	case DSP_MODE_SPK_V:
		telegramm = cnetz_transmit_telegramm_spk_v(cnetz);
		break;
	default:
		;
	}

	if (!telegramm)
		return NULL;

	opcode = telegramm->opcode;
	if (opcode == OPCODE_LR_R && cnetz->sched_lr_debugged)
		debug = 0;
	if (opcode == OPCODE_MLR_M && cnetz->sched_mlr_debugged)
		debug = 0;
	bits = assemble_telegramm(telegramm, debug);
	bits = encode(bits);
	bits = interleave(bits);

	/* invert, if polarity of the cell is negative */
	if (cnetz->negative_polarity) {
		int i;

		for (i = 0; i < 184; i++)
			bits[i] ^= 1;
	}

	if (opcode == OPCODE_LR_R && !cnetz->sched_lr_debugged)
		cnetz->sched_lr_debugged = 1;
	if (opcode == OPCODE_MLR_M && !cnetz->sched_mlr_debugged) {
		cnetz->sched_mlr_debugged = 1;
		LOGP(DFRAME, LOGL_INFO, "Subsequent IDLE frames are not shown, to prevent flooding the output.\n");
	}

	return bits;
}

