1462 lines
39 KiB
C
Executable File
1462 lines
39 KiB
C
Executable File
/* C-Netz FuVSt handling
|
|
*
|
|
* (C) 2020 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/>.
|
|
*/
|
|
|
|
/* Notes:
|
|
*
|
|
* The release timer will release the call, if no release response from BS has
|
|
* been received. This may happen due to signaling link error. Since there
|
|
* is no document available about message timeout conditions, I just use
|
|
* that timer when there is no response. I think that the base station does
|
|
* the same and releases the call, if no release response has been received.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <inttypes.h>
|
|
#include "../libsample/sample.h"
|
|
#include "../liblogging/logging.h"
|
|
#include "../liboptions/options.h"
|
|
#include "../libmobile/call.h"
|
|
#include "../libmobile/cause.h"
|
|
#include "../libmobile/get_time.h"
|
|
#include "../libmobile/console.h"
|
|
#include <osmocom/core/timer.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/cc/message.h>
|
|
#include "fuvst.h"
|
|
|
|
/* digital loopback test */
|
|
//#define DIGITAL_LOOPBACK
|
|
|
|
static int base_station_ready = 0;
|
|
extern double gebuehren;
|
|
extern int alarms;
|
|
extern int authentication;
|
|
extern int warmstart;
|
|
|
|
/* check if number is a valid station ID */
|
|
const char *cnetz_number_valid(const char *number)
|
|
{
|
|
/* assume that the number has valid length(s) and digits */
|
|
|
|
if (number[0] > '7')
|
|
return "Digit 1 (mobile country code) exceeds 7.";
|
|
if (number[7]) {
|
|
if ((number[1] - '0') == 0)
|
|
return "Digit 2 and 3 (mobile network code) of 8-digit number must be at least 10.";
|
|
if ((number[1] - '0') * 10 + (number[2] - '0') > 31)
|
|
return "Digit 2 and 3 (mobile network code) of 8-digit number exceed 31.";
|
|
if (atoi(number + 3) > 65535)
|
|
return "Digit 4 to 8 (mobile subscriber suffix) of 8-digit number exceed 65535.";
|
|
} else {
|
|
if (atoi(number + 2) > 65535)
|
|
return "Digit 3 to 7 (mobile subscriber suffix) of 7-digit number exceed 65535.";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int send_bit(void *inst)
|
|
{
|
|
fuvst_t *fuvst = (fuvst_t *)inst;
|
|
|
|
#ifdef DIGITAL_LOOPBACK
|
|
return 0;
|
|
#else
|
|
return mtp_send_bit(&fuvst->mtp);
|
|
#endif
|
|
}
|
|
|
|
static void receive_bit(void *inst, int bit)
|
|
{
|
|
fuvst_t *fuvst = (fuvst_t *)inst;
|
|
#ifdef DIGITAL_LOOPBACK
|
|
mtp_receive_bit(&fuvst->mtp, mtp_send_bit(&fuvst->mtp));
|
|
#else
|
|
mtp_receive_bit(&fuvst->mtp, bit);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* config handling
|
|
*/
|
|
|
|
static int message_send(uint8_t ident, uint8_t opcode, uint8_t *data, int len);
|
|
|
|
struct config {
|
|
uint8_t data[0x1800];
|
|
int loaded;
|
|
} conf;
|
|
|
|
/* init, even if no config shall be loaded */
|
|
void config_init(void)
|
|
{
|
|
memset(&conf, 0, sizeof(conf));
|
|
}
|
|
|
|
/* load config file */
|
|
int config_file(const char *filename)
|
|
{
|
|
FILE *fp = fopen(filename, "r");
|
|
int rc;
|
|
uint8_t byte;
|
|
|
|
if (!fp) {
|
|
LOGP(DTRANS, LOGL_ERROR, "Failed to open data base file: '%s'\n", filename);
|
|
return -EIO;
|
|
}
|
|
|
|
rc = fread(conf.data, 1, sizeof(conf.data), fp);
|
|
if (rc < (int)sizeof(conf.data)) {
|
|
fclose(fp);
|
|
LOGP(DTRANS, LOGL_ERROR, "Data base file shorter than %d bytes. This seems not to be a valid config file.\n", (int)sizeof(conf.data));
|
|
return -EIO;
|
|
}
|
|
|
|
rc = fread(&byte, 1, 1, fp);
|
|
if (rc == 1) {
|
|
fclose(fp);
|
|
LOGP(DTRANS, LOGL_ERROR, "Data base file larger than %d bytes. (Don't use the EEPROM config format, use the MSC config format.)\n", (int)sizeof(conf.data));
|
|
return -EIO;
|
|
}
|
|
|
|
fclose(fp);
|
|
conf.loaded = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* transfer config file towards base station */
|
|
static void config_send(uint8_t ident, uint8_t job, uint16_t offset, uint16_t length)
|
|
{
|
|
uint8_t opcode;
|
|
uint8_t *data;
|
|
int len;
|
|
int count, num;
|
|
uint8_t block[9];
|
|
int i, j;
|
|
uint32_t checksum = 0;
|
|
uint8_t rc = 1; /* Auftrag angenommen */
|
|
|
|
LOGP(DCNETZ, LOGL_NOTICE, "MSC requests data base block. (offset=%d, length=%d)\n", offset, length);
|
|
|
|
if (!conf.loaded) {
|
|
LOGP(DCNETZ, LOGL_ERROR, "MSC requests data base, but no file name given. Please give file name!\n");
|
|
error:
|
|
/* return error */
|
|
len = encode_xedbu_1(&opcode, &data, 16, job, 0);
|
|
message_send(ident, opcode, data, len);
|
|
return;
|
|
}
|
|
|
|
if (offset + length > sizeof(conf.data)) {
|
|
LOGP(DCNETZ, LOGL_ERROR, "Requested date out of range!\n");
|
|
goto error;
|
|
}
|
|
|
|
count = 0;
|
|
num = (length + 8) / 9;
|
|
|
|
/* header */
|
|
len = encode_xedbu_1(&opcode, &data, rc, job, num);
|
|
message_send(ident, opcode, data, len);
|
|
for (i = 0; i < num; i++) {
|
|
for (j = 0; j < 9; j++) {
|
|
if (count < length)
|
|
block[j] = conf.data[offset + count];
|
|
else
|
|
block[j] = 0x00;
|
|
checksum += block[j];
|
|
count++;
|
|
}
|
|
len = encode_xedbu_2(&opcode, &data, i + 1, job, block);
|
|
message_send(ident, opcode, data, len);
|
|
}
|
|
|
|
len = encode_xedbu_3(&opcode, &data, i + 1, job, offset, length, checksum);
|
|
message_send(ident, opcode, data, len);
|
|
}
|
|
|
|
/* convert metering pulse duration to a value that can be stored into base station */
|
|
static uint32_t duration2ticks(double duration)
|
|
{
|
|
double ticks, value;
|
|
|
|
/* this is the way the SIM-Rom and real network (trace) does it */
|
|
ticks = duration / 0.0375;
|
|
value = ticks / 16.0 * 4095.0; /* don't know why they did not use ticks * 256 */
|
|
|
|
return (uint32_t)value; // round down
|
|
}
|
|
|
|
/* transfer metering pulse duration table towards base station */
|
|
static void billing_send(uint8_t ident, uint8_t K)
|
|
{
|
|
uint32_t table[32];
|
|
uint16_t checksum = 0;
|
|
int i, last_i = 0;
|
|
uint8_t opcode;
|
|
uint8_t *data;
|
|
int len;
|
|
|
|
/* clear table */
|
|
memset(table, 0, sizeof(table));
|
|
|
|
/* set entries */
|
|
table[1] = duration2ticks(gebuehren);
|
|
|
|
/* generate checksum from table and remember last entry */
|
|
for (i = 0; i < 32; i++) {
|
|
checksum += i; // crazy: table index is included in checksum
|
|
checksum += table[i] & 0xff;
|
|
checksum += (table[i] >> 8) & 0xff;
|
|
checksum += (table[i] >> 16) & 0xff;
|
|
if (table[i])
|
|
last_i = i;
|
|
}
|
|
|
|
/* transfer all table entries with values in it */
|
|
for (i = 0; i < 32; i++) {
|
|
if (!table[i])
|
|
continue;
|
|
/* if i == last_i, set last entry flag */
|
|
len = encode_xgtau(&opcode, &data, i, table[i], (i == last_i) ? 1 : 0, K, checksum);
|
|
message_send(ident, opcode, data, len);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* emergency prefixes
|
|
*/
|
|
|
|
typedef struct emergency {
|
|
struct emergency *next;
|
|
char number[17];
|
|
} emergency_t;
|
|
|
|
emergency_t *emerg_list = NULL;
|
|
|
|
|
|
void add_emergency(const char *number)
|
|
{
|
|
emergency_t *emerg = NULL;
|
|
emergency_t **emergp;
|
|
|
|
emerg = calloc(1, sizeof(*emerg));
|
|
if (!emerg) {
|
|
LOGP(DTRANS, LOGL_ERROR, "No memory!\n");
|
|
return;
|
|
}
|
|
|
|
strncpy(emerg->number, number, sizeof(emerg->number) - 1);
|
|
|
|
/* attach to end of list, so first transaction is served first */
|
|
emergp = &emerg_list;
|
|
while (*emergp)
|
|
emergp = &((*emergp)->next);
|
|
*emergp = emerg;
|
|
}
|
|
|
|
static int check_emerg(const char *number)
|
|
{
|
|
emergency_t *emerg = emerg_list;
|
|
|
|
while (emerg) {
|
|
if (!strncmp(number, emerg->number, strlen(emerg->number)))
|
|
break;
|
|
emerg = emerg->next;
|
|
}
|
|
|
|
if (!emerg)
|
|
return 0;
|
|
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Emergency call, matching prefix '%s' in list of emergency numbers.\n", emerg->number);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* subscriber database
|
|
*/
|
|
|
|
typedef struct cnetz_database {
|
|
struct cnetz_database *next;
|
|
uint8_t futln_nat;
|
|
uint8_t futln_fuvst;
|
|
uint16_t futln_rest;
|
|
uint8_t chip;
|
|
} cnetz_db_t;
|
|
|
|
static cnetz_db_t *cnetz_db_head;
|
|
|
|
static cnetz_db_t *find_db(uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest)
|
|
{
|
|
cnetz_db_t *db;
|
|
|
|
/* search transaction for this subscriber */
|
|
db = cnetz_db_head;
|
|
while (db) {
|
|
if (db->futln_nat == futln_nat
|
|
&& db->futln_fuvst == futln_fuvst
|
|
&& db->futln_rest == futln_rest)
|
|
break;
|
|
db = db->next;
|
|
}
|
|
|
|
return db;
|
|
}
|
|
|
|
static void remove_db(uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest)
|
|
{
|
|
cnetz_db_t *db, **dbp;
|
|
|
|
db = find_db(futln_nat, futln_fuvst, futln_rest);
|
|
if (!db)
|
|
return;
|
|
|
|
/* uinlink */
|
|
dbp = &cnetz_db_head;
|
|
while (*dbp && *dbp != db)
|
|
dbp = &((*dbp)->next);
|
|
if (!(*dbp)) {
|
|
LOGP(DDB, LOGL_ERROR, "Subscriber not in list, please fix!!\n");
|
|
abort();
|
|
}
|
|
*dbp = db->next;
|
|
|
|
LOGP(DDB, LOGL_INFO, "Removing subscriber '%d,%d,%d' from database.\n", db->futln_nat, db->futln_fuvst, db->futln_rest);
|
|
|
|
/* remove */
|
|
free(db);
|
|
}
|
|
|
|
static void flush_db(void)
|
|
{
|
|
cnetz_db_t *db;
|
|
|
|
while ((db = cnetz_db_head)) {
|
|
LOGP(DDB, LOGL_INFO, "Removing subscriber '%d,%d,%d' from database.\n", db->futln_nat, db->futln_fuvst, db->futln_rest);
|
|
cnetz_db_head = db->next;
|
|
free(db);
|
|
}
|
|
}
|
|
|
|
static void add_db(uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest, uint8_t chip)
|
|
{
|
|
cnetz_db_t *db, **dbp;
|
|
|
|
db = find_db(futln_nat, futln_fuvst, futln_rest);
|
|
if (db)
|
|
return;
|
|
|
|
/* add */
|
|
db = calloc(1, sizeof(*db));
|
|
if (!db) {
|
|
LOGP(DDB, LOGL_ERROR, "No memory!\n");
|
|
return;
|
|
}
|
|
db->futln_nat = futln_nat;
|
|
db->futln_fuvst = futln_fuvst;
|
|
db->futln_rest = futln_rest;
|
|
db->chip = chip;
|
|
|
|
LOGP(DDB, LOGL_INFO, "Adding subscriber '%d,%d,%d' to database.\n", db->futln_nat, db->futln_fuvst, db->futln_rest);
|
|
|
|
/* attach to end of list */
|
|
dbp = &cnetz_db_head;
|
|
while (*dbp)
|
|
dbp = &((*dbp)->next);
|
|
*dbp = db;
|
|
}
|
|
|
|
/*
|
|
* transactions
|
|
*/
|
|
|
|
/* Release timeout */
|
|
#define RELEASE_TO 3,0
|
|
|
|
/* BSC originated Ident-Numbers */
|
|
#define IDENT_BSC_FROM 0x9f
|
|
#define IDENT_BSC_TO 0xff
|
|
static uint8_t last_bsc_ident = IDENT_BSC_TO;
|
|
|
|
enum call_state {
|
|
STATE_NULL = 0,
|
|
STATE_MO,
|
|
STATE_MO_QUEUE,
|
|
STATE_MT,
|
|
STATE_MT_QUEUE,
|
|
STATE_MT_ALERTING,
|
|
STATE_MO_CONNECT,
|
|
STATE_MT_CONNECT,
|
|
STATE_RELEASE,
|
|
};
|
|
|
|
typedef struct transaction {
|
|
struct transaction *next; /* pointer to next node in list */
|
|
enum call_state state; /* call state */
|
|
int mo; /* outgoing flag */
|
|
uint8_t ident; /* Identification Number */
|
|
uint8_t futln_nat; /* current station ID (3 values) */
|
|
uint8_t futln_fuvst;
|
|
uint16_t futln_rest;
|
|
int callref; /* callref for transaction */
|
|
int old_callref; /* remember callref after MNCC released */
|
|
int spk_nr;
|
|
fuvst_t *spk; /* assigned SPK */
|
|
char number[17]; /* dialed by mobile */
|
|
int sonderruf; /* an emergency call */
|
|
struct osmo_timer_list timer; /* release timer */
|
|
} transaction_t;
|
|
|
|
transaction_t *trans_list = NULL;
|
|
|
|
static const char *nut2rufnummer(uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest)
|
|
{
|
|
static char rufnummer[32]; /* make GCC happy (overflow check) */
|
|
|
|
sprintf(rufnummer, "%d%d%05d", futln_nat, futln_fuvst, futln_rest);
|
|
|
|
return rufnummer;
|
|
}
|
|
|
|
static const char *transaction2rufnummer(transaction_t *trans)
|
|
{
|
|
return nut2rufnummer(trans->futln_nat, trans->futln_fuvst, trans->futln_rest);
|
|
}
|
|
|
|
const char *state_name(enum call_state state)
|
|
{
|
|
static char invalid[16];
|
|
|
|
switch (state) {
|
|
case STATE_NULL:
|
|
return "NULL";
|
|
case STATE_MO:
|
|
return "MO (Setup)";
|
|
case STATE_MO_QUEUE:
|
|
return "MO (Queue)";
|
|
case STATE_MT:
|
|
return "MT (Setup)";
|
|
case STATE_MT_QUEUE:
|
|
return "MT (Queue)";
|
|
case STATE_MT_ALERTING:
|
|
return "MT (Alerting)";
|
|
case STATE_MO_CONNECT:
|
|
return "MO (Connected)";
|
|
case STATE_MT_CONNECT:
|
|
return "MT (Connected)";
|
|
case STATE_RELEASE:
|
|
return "Release";
|
|
}
|
|
|
|
sprintf(invalid, "invalid(%d)", state);
|
|
return invalid;
|
|
}
|
|
|
|
void display_status(void)
|
|
{
|
|
sender_t *sender;
|
|
fuvst_t *fuvst;
|
|
transaction_t *trans;
|
|
|
|
display_status_start();
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
fuvst = (fuvst_t *) sender;
|
|
if (fuvst->chan_type == CHAN_TYPE_SPK) {
|
|
display_status_channel(fuvst->sender.kanal, "SpK", (fuvst->callref) ? "BUSY" : "IDLE");
|
|
for (trans = trans_list; trans; trans = trans->next) {
|
|
if (trans->spk != fuvst)
|
|
continue;
|
|
display_status_subscriber(transaction2rufnummer(trans), state_name(trans->state));
|
|
}
|
|
} else {
|
|
display_status_channel(fuvst->sender.kanal, "ZZK", (fuvst->link) ? "Link UP" : "Link DOWN");
|
|
for (trans = trans_list; trans; trans = trans->next) {
|
|
if (trans->spk)
|
|
continue;
|
|
display_status_subscriber(transaction2rufnummer(trans), state_name(trans->state));
|
|
}
|
|
}
|
|
}
|
|
display_status_end();
|
|
}
|
|
|
|
static void new_call_state(transaction_t *trans, enum call_state new_state)
|
|
{
|
|
if (trans->state == new_state)
|
|
return;
|
|
LOGP(DTRANS, LOGL_INFO, "State change: %s -> %s\n", state_name(trans->state), state_name(new_state));
|
|
trans->state = new_state;
|
|
display_status();
|
|
}
|
|
|
|
transaction_t *search_transaction_number(uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest)
|
|
{
|
|
transaction_t *trans = trans_list;
|
|
|
|
while (trans) {
|
|
if (trans->futln_nat == futln_nat
|
|
&& trans->futln_fuvst == futln_fuvst
|
|
&& trans->futln_rest == futln_rest) {
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_DEBUG, "Found transaction for subscriber '%s'\n", rufnummer);
|
|
return trans;
|
|
}
|
|
trans = trans->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
transaction_t *search_transaction_ident(uint8_t ident)
|
|
{
|
|
transaction_t *trans = trans_list;
|
|
|
|
while (trans) {
|
|
if (trans->ident == ident) {
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_DEBUG, "Found transaction for subscriber '%s'\n", rufnummer);
|
|
return trans;
|
|
}
|
|
trans = trans->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
transaction_t *search_transaction_callref(int callref)
|
|
{
|
|
transaction_t *trans = trans_list;
|
|
|
|
/* just in case, this should not happen */
|
|
if (!callref)
|
|
return NULL;
|
|
while (trans) {
|
|
if (trans->callref == callref) {
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_DEBUG, "Found transaction for subscriber '%s'\n", rufnummer);
|
|
return trans;
|
|
}
|
|
trans = trans->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* destroy transaction */
|
|
static void destroy_transaction(transaction_t *trans)
|
|
{
|
|
transaction_t **transp;
|
|
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_INFO, "Destroying transaction for subscriber '%s'\n", rufnummer);
|
|
|
|
osmo_timer_del(&trans->timer);
|
|
|
|
/* check for old callref (before removal) then detach SPK
|
|
* if SPK has been reused by BS, our old callref will not match,
|
|
* so that we don't detach.
|
|
*/
|
|
if (trans->spk && trans->spk->callref == trans->old_callref)
|
|
trans->spk->callref = 0;
|
|
|
|
/* unlink */
|
|
transp = &trans_list;
|
|
while (*transp && *transp != trans)
|
|
transp = &((*transp)->next);
|
|
if (!(*transp)) {
|
|
LOGP(DTRANS, LOGL_ERROR, "Transaction not in list, please fix!!\n");
|
|
abort();
|
|
}
|
|
*transp = trans->next;
|
|
|
|
free(trans);
|
|
|
|
display_status();
|
|
}
|
|
|
|
/* Timeout handling */
|
|
void trans_timeout(void *data)
|
|
{
|
|
transaction_t *trans = data;
|
|
|
|
LOGP(DTRANS, LOGL_NOTICE, "Releasing transaction due to timeout.\n");
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, CAUSE_NORMAL);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
}
|
|
|
|
/* create new transaction */
|
|
static transaction_t *create_transaction(uint8_t ident, uint8_t futln_nat, uint8_t futln_fuvst, uint16_t futln_rest, int mo)
|
|
{
|
|
transaction_t *trans = NULL;
|
|
transaction_t **transp;
|
|
|
|
trans = search_transaction_number(futln_nat, futln_fuvst, futln_rest);
|
|
if (trans && mo) {
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_NOTICE, "Found already pending transaction for subscriber '%s', dropping that!\n", rufnummer);
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, CAUSE_NORMAL);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
trans = NULL;
|
|
}
|
|
if (trans) {
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_NOTICE, "Found already pending transaction for subscriber '%s', we are busy!\n", rufnummer);
|
|
return NULL;
|
|
}
|
|
|
|
trans = calloc(1, sizeof(*trans));
|
|
if (!trans) {
|
|
LOGP(DTRANS, LOGL_ERROR, "No memory!\n");
|
|
return NULL;
|
|
}
|
|
|
|
trans->mo = mo;
|
|
trans->ident = ident;
|
|
trans->futln_nat = futln_nat;
|
|
trans->futln_fuvst = futln_fuvst;
|
|
trans->futln_rest = futln_rest;
|
|
|
|
osmo_timer_setup(&trans->timer, trans_timeout, trans);
|
|
|
|
const char *rufnummer = transaction2rufnummer(trans);
|
|
LOGP(DTRANS, LOGL_INFO, "Creating transaction for subscriber '%s'\n", rufnummer);
|
|
|
|
/* attach to end of list, so first transaction is served first */
|
|
transp = &trans_list;
|
|
while (*transp)
|
|
transp = &((*transp)->next);
|
|
*transp = trans;
|
|
|
|
return trans;
|
|
}
|
|
|
|
/* get free Ident-Number or 0 if not available */
|
|
static uint8_t get_free_ident(void)
|
|
{
|
|
transaction_t *trans;
|
|
int i;
|
|
|
|
/* count as many times as our ID range */
|
|
for (i = IDENT_BSC_FROM; i <= IDENT_BSC_TO; i++) {
|
|
/* select next ident number */
|
|
if (last_bsc_ident == IDENT_BSC_TO)
|
|
last_bsc_ident = IDENT_BSC_FROM;
|
|
else
|
|
last_bsc_ident++;
|
|
|
|
/* check if that number is free */
|
|
trans = trans_list;
|
|
while (trans) {
|
|
if (trans->ident == last_bsc_ident)
|
|
break;
|
|
trans = trans->next;
|
|
}
|
|
|
|
/* if free (not found), return it */
|
|
if (!trans)
|
|
return last_bsc_ident;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static fuvst_t *get_zzk(int num)
|
|
{
|
|
sender_t *sender;
|
|
fuvst_t *fuvst;
|
|
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
fuvst = (fuvst_t *) sender;
|
|
if (fuvst->chan_type != CHAN_TYPE_ZZK)
|
|
continue;
|
|
if (!fuvst->link)
|
|
continue;
|
|
if (!num || num == fuvst->chan_num)
|
|
return fuvst;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static fuvst_t *get_spk(uint8_t Q)
|
|
{
|
|
sender_t *sender;
|
|
fuvst_t *fuvst;
|
|
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
fuvst = (fuvst_t *) sender;
|
|
if (fuvst->chan_type == CHAN_TYPE_SPK
|
|
&& fuvst->chan_num == Q)
|
|
return fuvst;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Convert 'Ausloesegrund' of C-Netz base station to ISDN cause */
|
|
int cnetz_fufst2cause(uint8_t X)
|
|
{
|
|
switch (X) {
|
|
case 0: /* undefiniert */
|
|
case 1: /* Einh.A (A-BS) */
|
|
case 2: /* Einh.B (B-BS) */
|
|
return CAUSE_NORMAL;
|
|
case 3: /* Tln-besetzt (A-BS) */
|
|
return CAUSE_BUSY;
|
|
case 4: /* Gassenbesetzt */
|
|
case 5: /* Time-Out: kein Sprechkanal */
|
|
case 8: /* WS ist blockiert (Time out) */
|
|
return CAUSE_NOCHANNEL;
|
|
case 9: /* Funk-Tln nicht aktiv */
|
|
return CAUSE_OUTOFORDER;
|
|
case 27: /* Kein Melden B (B-BS), Infobox aktiviert */
|
|
return CAUSE_NOANSWER;
|
|
default: /* alles andere */
|
|
return CAUSE_TEMPFAIL;
|
|
}
|
|
}
|
|
|
|
/* Convert ISDN cause to 'Ausloesegrund' of C-Netz mobile station */
|
|
uint8_t cnetz_cause2futln(int cause)
|
|
{
|
|
switch (cause) {
|
|
case CAUSE_NORMAL:
|
|
case CAUSE_BUSY:
|
|
case CAUSE_NOANSWER:
|
|
return 1;
|
|
case CAUSE_OUTOFORDER:
|
|
case CAUSE_INVALNUMBER:
|
|
case CAUSE_NOCHANNEL:
|
|
case CAUSE_TEMPFAIL:
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* MTP data message to lower layer */
|
|
static int message_send(uint8_t ident, uint8_t opcode, uint8_t *data, int len)
|
|
{
|
|
uint8_t slc;
|
|
fuvst_t *zzk;
|
|
|
|
/* hunt for ZZK (1 or 2), depending on LSB of ident (load sharing) */
|
|
zzk = get_zzk((ident & 1) + 1);
|
|
/* if not found, hunt for any ZZK */
|
|
if (!zzk)
|
|
zzk = get_zzk(0);
|
|
if (!zzk) {
|
|
LOGP(DCNETZ, LOGL_ERROR, "No ZZK or link down!\n");
|
|
return -EIO;
|
|
}
|
|
|
|
uint8_t buffer[len + 2];
|
|
|
|
if (loglevel == LOGL_DEBUG || opcode != OPCODE_XEDBU)
|
|
LOGP(DCNETZ, LOGL_INFO, "TX Message to BS: link=%s ident=0x%02x OP=%02XH %s\n", zzk->sender.kanal, ident, opcode, osmo_hexdump(data, len));
|
|
|
|
/* assemble Ident, swap Opcode and add data */
|
|
slc = ident & 0xf;
|
|
buffer[0] = ident >> 4;
|
|
buffer[1] = (opcode >> 4) | (opcode << 4);
|
|
if (len)
|
|
memcpy(buffer + 2, data, len);
|
|
data = buffer;
|
|
len += 2;
|
|
|
|
return mtp_send(&zzk->mtp, MTP_PRIM_DATA, slc, data, len);
|
|
}
|
|
|
|
static void release_for_emergency(void)
|
|
{
|
|
sender_t *sender;
|
|
fuvst_t *fuvst;
|
|
transaction_t *trans, *last_trans = NULL;
|
|
uint8_t opcode, *data;
|
|
int len;
|
|
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
fuvst = (fuvst_t *) sender;
|
|
if (fuvst->chan_type != CHAN_TYPE_SPK)
|
|
continue;
|
|
if (!fuvst->callref)
|
|
break;
|
|
for (trans = trans_list; trans; trans = trans->next) {
|
|
if (trans->spk == fuvst)
|
|
break;
|
|
}
|
|
if (trans && !trans->sonderruf)
|
|
last_trans = trans;
|
|
}
|
|
|
|
/* found idle channel */
|
|
if (sender) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Emergency call received. We have a free channel available.\n");
|
|
return;
|
|
}
|
|
|
|
/* found no normal call (no emergency) */
|
|
if (!last_trans) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Emergency call received. We cannot free a channel, because there is no non-emergency call.\n");
|
|
return;
|
|
}
|
|
|
|
/* releasing the last call in list */
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Emergency call received. We free a channel.\n");
|
|
|
|
len = encode_aau(&opcode, &data, trans->spk_nr, 0, cnetz_cause2futln(CAUSE_NORMAL));
|
|
message_send(trans->ident, opcode, data, len);
|
|
call_up_release(trans->callref, CAUSE_NORMAL);
|
|
trans->callref = 0;
|
|
new_call_state(trans, STATE_RELEASE);
|
|
osmo_timer_schedule(&trans->timer, RELEASE_TO);
|
|
}
|
|
|
|
/* MTP data message from lower layer */
|
|
static void message_receive(fuvst_t *zzk, uint8_t ident, uint8_t opcode, uint8_t *data, int len)
|
|
{
|
|
transaction_t *trans;
|
|
uint16_t T = 0;
|
|
uint8_t F = 0, U = 0, N = 0;
|
|
uint8_t C = 0;
|
|
uint8_t B = 0;
|
|
uint8_t Q = 0, S = 0;
|
|
uint8_t V = 0;
|
|
uint8_t e = 0;
|
|
uint64_t n = 0, a = 0;
|
|
uint8_t X = 0;
|
|
uint8_t PJ = 0;
|
|
uint16_t D = 0, L = 0;
|
|
uint16_t s = 0;
|
|
uint8_t u = 0, b = 0, l = 0;
|
|
uint16_t T_array[3];
|
|
uint8_t U_array[3], N_array[3], l_array[3];
|
|
int i, num;
|
|
char number[17];
|
|
|
|
if (loglevel == LOGL_DEBUG || opcode != OPCODE_YLSMF)
|
|
LOGP(DCNETZ, LOGL_INFO, "RX Message from BS: link=%s ident=0x%02x OP=%02XH %s\n", zzk->sender.kanal, ident, opcode, osmo_hexdump(data, len));
|
|
|
|
switch (opcode) {
|
|
case OPCODE_SWAF: /* BS restarts */
|
|
decode_swaf(data, len, &V, &N, &U, &F, &C, &B);
|
|
len = encode_swqu(&opcode, &data, 0); /* must be 0, or all subscribers will be kicked */
|
|
message_send(ident, opcode, data, len);
|
|
#if 0
|
|
does not work
|
|
/* request database */
|
|
encode_sadau(&opcode);
|
|
message_send(5, opcode, NULL, 0);
|
|
#endif
|
|
if (warmstart) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Forcing a warm start and load the config...\n");
|
|
warmstart = 0;
|
|
len = encode_yaaau(&opcode, &data, 42);
|
|
message_send(0, opcode, data, len);
|
|
}
|
|
break;
|
|
case OPCODE_SSSAF: /* channel allocation map */
|
|
decode_sssaf(data, len);
|
|
encode_sssqu(&opcode);
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
case OPCODE_SSAF: /* lock a channel */
|
|
decode_ssaf(data, len, &S);
|
|
len = encode_ssqu(&opcode, &data, S);
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
case OPCODE_SFAF: /* unlock a channel */
|
|
decode_sfaf(data, len, &S);
|
|
len = encode_sfqu(&opcode, &data, S);
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
case OPCODE_SUAF: /* set time */
|
|
decode_suaf(data, len, &V, &N, &U, &F, &C, &B);
|
|
len = encode_suqu(&opcode, &data, 1, 0, floor(get_time()));
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
case OPCODE_SVAF: /* base station ready */
|
|
decode_svaf(data, len);
|
|
len = encode_svqu(&opcode, &data);
|
|
message_send(ident, opcode, data, len);
|
|
base_station_ready = 1;
|
|
break;
|
|
case OPCODE_YLSAF: /* alarm request */
|
|
decode_ylsaf(data, len);
|
|
if (!alarms)
|
|
break;
|
|
len = encode_ylsmu(&opcode, &data);
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
case OPCODE_YLSMF: /* alarm data */
|
|
decode_ylsmf(data, len, &N, &C, &zzk->SM);
|
|
if (N == 4)
|
|
memset(&zzk->SM, 0, sizeof(zzk->SM));
|
|
break;
|
|
case OPCODE_YLSEF: /* alarm list ends */
|
|
decode_ylsef(data, len);
|
|
break;
|
|
case OPCODE_STDAF: /* billing information */
|
|
decode_stdaf(data, len);
|
|
/* active table */
|
|
billing_send(ident, 3);
|
|
/* passive table */
|
|
billing_send(ident, 4);
|
|
break;
|
|
case OPCODE_EBAF: /* enter BS (inscription) */
|
|
decode_ebaf(data, len, &T, &U, &N, &s, &u, &b, &l);
|
|
add_db(N, U, T, l);
|
|
console_inscription(nut2rufnummer(N, U, T));
|
|
len = encode_ebpqu(&opcode, &data);
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
case OPCODE_ABAF: /* leave BS */
|
|
decode_abaf(data, len, &T, &U, &N);
|
|
remove_db(N, U, T);
|
|
break;
|
|
case OPCODE_GVAF: /* MO call */
|
|
decode_gvaf(data, len, &T, &U, &N, number);
|
|
LOGP(DCNETZ, LOGL_INFO, "Call from mobile.\n");
|
|
goto outgoing;
|
|
case OPCODE_GVWAF: /* MO call (queue) */
|
|
decode_gvaf(data, len, &T, &U, &N, number);
|
|
LOGP(DCNETZ, LOGL_INFO, "Call from mobile (queue).\n");
|
|
outgoing:
|
|
trans = create_transaction(ident, N, U, T, 1);
|
|
if (!trans) {
|
|
/* reject, if no transaction */
|
|
len = encode_gvnqu(&opcode, &data, 0, cnetz_cause2futln(CAUSE_TEMPFAIL));
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
}
|
|
strcpy(trans->number, number);
|
|
trans->sonderruf = check_emerg(number);
|
|
new_call_state(trans, (opcode == OPCODE_GVAF) ? STATE_MO : STATE_MO_QUEUE);
|
|
len = encode_gvpqu(&opcode, &data, trans->sonderruf, authentication);
|
|
message_send(ident, OPCODE_GVPQU, data, len);
|
|
/* release a call, if all SPK are in use */
|
|
release_for_emergency();
|
|
break;
|
|
case OPCODE_KVWQF: /* MT call ack (queue) */
|
|
decode_kvwqf(data, len);
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans)
|
|
break;
|
|
LOGP(DCNETZ, LOGL_INFO, "Call to mobile is alerting (queue).\n");
|
|
new_call_state(trans, STATE_MT_QUEUE);
|
|
if (trans->callref)
|
|
call_up_alerting(trans->callref);
|
|
break;
|
|
case OPCODE_KVBAF: /* MT call answer */
|
|
decode_kvbaf(data, len);
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans)
|
|
break;
|
|
LOGP(DCNETZ, LOGL_INFO, "Call to mobile has been answered.\n");
|
|
new_call_state(trans, STATE_MT_CONNECT);
|
|
if (trans->callref)
|
|
call_up_answer(trans->callref, transaction2rufnummer(trans));
|
|
break;
|
|
case OPCODE_STAF: /* assign channel */
|
|
decode_staf(data, len, &Q, &V, &e, &n);
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans) {
|
|
/* race condition: we may get a channel after releaseing a call, so we must release that channel */
|
|
len = encode_aau(&opcode, &data, Q, 0, cnetz_cause2futln(CAUSE_NOCHANNEL));
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
}
|
|
trans->spk = get_spk(Q);
|
|
trans->spk_nr = Q;
|
|
/* SPK not exist, release */
|
|
if (!trans->spk) {
|
|
LOGP(DCNETZ, LOGL_ERROR, "SpK '%d' requested by BS not configured, please configure all SpK that base station has available!\n", Q);
|
|
len = encode_stnqu(&opcode, &data, Q);
|
|
message_send(ident, opcode, data, len);
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, CAUSE_NORMAL);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
break;
|
|
}
|
|
if (trans->mo)
|
|
len = encode_stpqu(&opcode, &data, Q, 1, 0, 0, 0, 0, 0, 1);
|
|
else
|
|
len = encode_stpqu(&opcode, &data, Q, 0, 0, 0, 0, 0, 0, 0);
|
|
message_send(ident, opcode, data, len);
|
|
/* no callref == outgoing call */
|
|
if (!trans->callref) {
|
|
LOGP(DCNETZ, LOGL_INFO, "Setup call to network. (Ident = %d, FuTln=%s, number=%s)\n", ident, transaction2rufnummer(trans), trans->number);
|
|
trans->callref = trans->old_callref = call_up_setup(transaction2rufnummer(trans), trans->number, OSMO_CC_NETWORK_CNETZ_NONE, "");
|
|
} else {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Call to mobile is alerting.\n");
|
|
new_call_state(trans, STATE_MT_ALERTING);
|
|
call_up_alerting(trans->callref);
|
|
}
|
|
trans->spk->callref = trans->callref;
|
|
LOGP(DCNETZ, LOGL_INFO, "Assigned SpK %d to call.\n", trans->spk_nr);
|
|
break;
|
|
case OPCODE_APF: /* auth response */
|
|
decode_apf(data, len, &Q, &a);
|
|
break;
|
|
case OPCODE_FAF: /* MCID request */
|
|
decode_faf(data, len);
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Fangen (MCID) was activated by BS.\n");
|
|
break;
|
|
case OPCODE_NAF: /* incoming release (before SPK assignment) */
|
|
decode_naf(data, len, &X);
|
|
len = encode_equ(&opcode, &data);
|
|
message_send(ident, opcode, data, len);
|
|
/* get transaction */
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans)
|
|
break;
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Call released by BS.\n");
|
|
new_call_state(trans, STATE_RELEASE);
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, cnetz_fufst2cause(X));
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
break;
|
|
case OPCODE_EQF: /* outgoing release acknowledge (after SPK assignment) */
|
|
decode_eqf(data, len);
|
|
/* get transaction, should not exist anymore */
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans)
|
|
break;
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, CAUSE_NORMAL);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
break;
|
|
case OPCODE_AAF: /* incoming release (after SPK assignment) */
|
|
decode_aaf(data, len, &Q, &X);
|
|
/* get transaction */
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans) {
|
|
/* race condition: if we have a release collision, we don't need to ack */
|
|
break;
|
|
}
|
|
len = encode_aqu(&opcode, &data, Q);
|
|
message_send(ident, opcode, data, len);
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, cnetz_fufst2cause(X));
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Call released by BS.\n");
|
|
new_call_state(trans, STATE_RELEASE);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
break;
|
|
case OPCODE_AQF: /* outgoing release acknowledge (after SPK assignment) */
|
|
decode_aqf(data, len, &Q);
|
|
/* get transaction, should not exist anymore */
|
|
trans = search_transaction_ident(ident);
|
|
if (!trans)
|
|
break;
|
|
if (trans->callref)
|
|
call_up_release(trans->callref, CAUSE_NORMAL);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
break;
|
|
case OPCODE_XADBF: /* transfer config */
|
|
decode_xadbf(data, len, &PJ, &D, &L);
|
|
config_send(ident, PJ, D, L);
|
|
break;
|
|
case OPCODE_SADQF: /* transfer of inscription list (aktivdatei) */
|
|
num = decode_sadqf(data, len, &s, &e, l_array, T_array, U_array, N_array);
|
|
if (s == 0)
|
|
flush_db();
|
|
for (i = 0; i < num; i++)
|
|
add_db(N_array[i], U_array[i], T_array[i], l_array[i]);
|
|
len = encode_ebpqu(&opcode, &data);
|
|
message_send(ident, opcode, data, len);
|
|
break;
|
|
default:
|
|
LOGP(DCNETZ, LOGL_INFO, "RX Message from BS with unknown OPcode: %02XH\n", opcode);
|
|
}
|
|
}
|
|
|
|
/* receive MTP primitive from lower layer */
|
|
static void mtp_receive(void *inst, enum mtp_prim prim, uint8_t slc, uint8_t *data, int len)
|
|
{
|
|
fuvst_t *zzk = (fuvst_t *)inst;
|
|
uint8_t ident;
|
|
uint8_t opcode;
|
|
const char *cause_text;
|
|
|
|
switch (prim) {
|
|
case MTP_PRIM_OUT_OF_SERVICE:
|
|
switch (*data) {
|
|
case MTP_CAUSE_ALIGNMENT_TIMEOUT:
|
|
cause_text = "MTP link '%s' alignment timeout! Trying again.\n";
|
|
break;
|
|
case MTP_CAUSE_LINK_FAILURE_LOCAL:
|
|
cause_text = "MTP link '%s' from remote to local failed! Starting recovery.\n";
|
|
break;
|
|
case MTP_CAUSE_LINK_FAILURE_REMOTE:
|
|
cause_text = "MTP link '%s' from local to remote failed! Starting recovery.\n";
|
|
break;
|
|
case MTP_CAUSE_PROVING_FAILURE_LOCAL:
|
|
cause_text = "MTP link '%s' proving failed locally! Trying again.\n";
|
|
break;
|
|
case MTP_CAUSE_PROVING_FAILURE_REMOTE:
|
|
cause_text = "MTP link '%s' proving failed remotely! Trying again.\n";
|
|
break;
|
|
case MTP_CAUSE_PROVING_TIMEOUT:
|
|
cause_text = "MTP link '%s' proving timeout! Trying again.\n";
|
|
break;
|
|
default:
|
|
cause_text = "MTP link '%s' failed! Trying again.\n";
|
|
}
|
|
LOGP(DCNETZ, LOGL_NOTICE, cause_text, zzk->sender.kanal);
|
|
mtp_send(&zzk->mtp, MTP_PRIM_START, 0, NULL, 0);
|
|
zzk->link = 0;
|
|
display_status();
|
|
break;
|
|
case MTP_PRIM_IN_SERVICE:
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Link '%s' established.\n", zzk->sender.kanal);
|
|
zzk->link = 1;
|
|
display_status();
|
|
break;
|
|
case MTP_PRIM_REMOTE_PROCESSOR_OUTAGE:
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Link '%s' indicates remote processor outage.\n", zzk->sender.kanal);
|
|
break;
|
|
case MTP_PRIM_REMOTE_PROCESSOR_RECOVERED:
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Link '%s' indicates remote processor outage is recovered.\n", zzk->sender.kanal);
|
|
break;
|
|
case MTP_PRIM_DATA:
|
|
if (len < 2) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "No Opcode, message too short!\n");
|
|
return;
|
|
}
|
|
|
|
/* assemble Ident swap Opcode and remove from data */
|
|
ident = slc | ((*data++) << 4);
|
|
opcode = (*data >> 4) | (*data << 4);
|
|
data++;
|
|
len -= 2;
|
|
|
|
message_receive(zzk, ident, opcode, data, len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Create transceiver instance and link to a list. */
|
|
int fuvst_create(const char *kanal, enum fuvst_chan_type chan_type, const char *audiodev, int samplerate, double rx_gain, double tx_gain, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, int ignore_link_monitor, uint8_t sio, uint16_t local_pc, uint16_t remote_pc)
|
|
{
|
|
fuvst_t *fuvst;
|
|
int rc;
|
|
char chan_name[64];
|
|
|
|
if (chan_type == CHAN_TYPE_ZZK)
|
|
sprintf(chan_name, "ZZK-%s", kanal);
|
|
else
|
|
sprintf(chan_name, "SPK-%s", kanal);
|
|
|
|
fuvst = calloc(1, sizeof(fuvst_t));
|
|
if (!fuvst) {
|
|
LOGP(DCNETZ, LOGL_ERROR, "No memory!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
LOGP(DCNETZ, LOGL_DEBUG, "Creating 'C-Netz' instance for 'Kanal' = %s (sample rate %d).\n", chan_name, samplerate);
|
|
|
|
/* init general part of transceiver */
|
|
/* do not enable emphasis, since it is done by fuvst code, not by common sender code */
|
|
rc = sender_create(&fuvst->sender, options_strdup(chan_name), 0, 0, audiodev, 0, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE);
|
|
if (rc < 0) {
|
|
LOGP(DCNETZ, LOGL_ERROR, "Failed to init transceiver process!\n");
|
|
goto error;
|
|
}
|
|
fuvst->chan_num = atoi(kanal);
|
|
fuvst->chan_type = chan_type;
|
|
|
|
sender_set_fm(&fuvst->sender, 1.0, 4000.0, 1.0, 1.0);
|
|
|
|
if (fuvst->chan_type == CHAN_TYPE_ZZK) {
|
|
/* create link */
|
|
rc = mtp_init(&fuvst->mtp, options_strdup(chan_name), fuvst, mtp_receive, 4800, ignore_link_monitor, sio, local_pc, remote_pc);
|
|
if (rc < 0)
|
|
goto error;
|
|
/* power on stack */
|
|
mtp_send(&fuvst->mtp, MTP_PRIM_POWER_ON, 0, NULL, 0);
|
|
/* use emegency alignment */
|
|
mtp_send(&fuvst->mtp, MTP_PRIM_EMERGENCY, 0, NULL, 0);
|
|
/* start link */
|
|
mtp_send(&fuvst->mtp, MTP_PRIM_START, 0, NULL, 0);
|
|
|
|
/* create modem */
|
|
rc = v27_modem_init(&fuvst->modem, fuvst, send_bit, receive_bit, samplerate, 1);
|
|
if (rc < 0)
|
|
goto error;
|
|
}
|
|
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Created 'Kanal' %s\n", chan_name);
|
|
|
|
display_status();
|
|
|
|
return 0;
|
|
|
|
error:
|
|
fuvst_destroy(&fuvst->sender);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Destroy transceiver instance and unlink from list. */
|
|
void fuvst_destroy(sender_t *sender)
|
|
{
|
|
fuvst_t *fuvst = (fuvst_t *) sender;
|
|
|
|
LOGP(DCNETZ, LOGL_DEBUG, "Destroying 'C-Netz' instance for 'Kanal' = %s.\n", sender->kanal);
|
|
|
|
if (fuvst->chan_type == CHAN_TYPE_ZZK) {
|
|
mtp_exit(&fuvst->mtp);
|
|
|
|
v27_modem_exit(&fuvst->modem);
|
|
}
|
|
|
|
sender_destroy(&fuvst->sender);
|
|
free(fuvst);
|
|
}
|
|
|
|
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
|
{
|
|
fuvst_t *fuvst = (fuvst_t *) sender;
|
|
int input_num;
|
|
|
|
memset(power, 1, length);
|
|
|
|
if (fuvst->chan_type == CHAN_TYPE_ZZK)
|
|
v27_modem_send(&fuvst->modem, samples, length);
|
|
else {
|
|
input_num = samplerate_upsample_input_num(&sender->srstate, length);
|
|
jitter_load(&sender->dejitter, samples, input_num);
|
|
samplerate_upsample(&sender->srstate, samples, input_num, samples, length);
|
|
}
|
|
}
|
|
|
|
void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db)
|
|
{
|
|
fuvst_t *fuvst = (fuvst_t *) sender;
|
|
sample_t *spl;
|
|
int pos;
|
|
int i;
|
|
|
|
if (fuvst->chan_type == CHAN_TYPE_ZZK)
|
|
v27_modem_receive(&fuvst->modem, samples, length);
|
|
else {
|
|
/* Forward audio to network (call process). */
|
|
if (fuvst->callref) {
|
|
int count;
|
|
|
|
count = samplerate_downsample(&fuvst->sender.srstate, samples, length);
|
|
spl = fuvst->sender.rxbuf;
|
|
pos = fuvst->sender.rxbuf_pos;
|
|
for (i = 0; i < count; i++) {
|
|
spl[pos++] = samples[i];
|
|
if (pos == 160) {
|
|
call_up_audio(fuvst->callref, spl, 160);
|
|
pos = 0;
|
|
}
|
|
}
|
|
fuvst->sender.rxbuf_pos = pos;
|
|
} else
|
|
fuvst->sender.rxbuf_pos = 0;
|
|
}
|
|
}
|
|
|
|
/* Receive audio from call instance. */
|
|
void call_down_audio(int callref, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, sample_t *samples, int count)
|
|
{
|
|
sender_t *sender;
|
|
fuvst_t *fuvst;
|
|
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
fuvst = (fuvst_t *) sender;
|
|
if (fuvst->callref == callref)
|
|
break;
|
|
}
|
|
if (!sender)
|
|
return;
|
|
|
|
if (fuvst->callref)
|
|
jitter_save(&fuvst->sender.dejitter, samples, count, 1, sequence, timestamp, ssrc);
|
|
}
|
|
|
|
void call_down_clock(void) {}
|
|
|
|
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
|
|
{
|
|
uint8_t futln_nat;
|
|
uint8_t futln_fuvst;
|
|
int futln_rest; /* use int for checking size > 65535 */
|
|
int len;
|
|
transaction_t *trans;
|
|
uint8_t ident;
|
|
uint8_t opcode, *data;
|
|
|
|
/* 1. split number into elements */
|
|
futln_nat = dialing[0] - '0';
|
|
if (dialing[7]) {
|
|
futln_fuvst = (dialing[1] - '0') * 10 + (dialing[2] - '0');
|
|
futln_rest = atoi(dialing + 3);
|
|
} else {
|
|
futln_fuvst = dialing[1] - '0';
|
|
futln_rest = atoi(dialing + 2);
|
|
}
|
|
|
|
/* 2. base station ready? */
|
|
if (!base_station_ready) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Outgoing call not possible, base station not ready, rejecting!\n");
|
|
return -CAUSE_TEMPFAIL;
|
|
}
|
|
|
|
/* 3. create transaction */
|
|
ident = get_free_ident();
|
|
if (!ident) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Outgoing call not possible, no free Ident code?? What the hack?\n");
|
|
return -CAUSE_TEMPFAIL;
|
|
}
|
|
trans = create_transaction(ident, futln_nat, futln_fuvst, futln_rest, 0);
|
|
if (!trans) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Outgoing call not possible, Transaction already exists: Subscriber busy!\n");
|
|
return -CAUSE_BUSY;
|
|
}
|
|
trans->callref = trans->old_callref = callref;
|
|
|
|
/* 4. start call */
|
|
len = encode_kvau(&opcode, &data, futln_rest, futln_fuvst, futln_nat, 0, authentication);
|
|
message_send(trans->ident, opcode, data, len);
|
|
LOGP(DCNETZ, LOGL_INFO, "Send call for mobile towards BS. (Ident = %d, FuTln=%s)\n", ident, transaction2rufnummer(trans));
|
|
new_call_state(trans, STATE_MT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void call_down_answer(int callref)
|
|
{
|
|
transaction_t *trans;
|
|
uint8_t opcode, *data;
|
|
int len;
|
|
|
|
trans = search_transaction_callref(callref);
|
|
if (!trans) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Answer to unknown callref.\n");
|
|
return;
|
|
}
|
|
|
|
len = encode_gstau(&opcode, &data, trans->spk_nr, 1, 0, 1, 1, 0);
|
|
message_send(trans->ident, opcode, data, len);
|
|
new_call_state(trans, STATE_MO_CONNECT);
|
|
}
|
|
|
|
static void _disconnect_release(transaction_t *trans, int callref, int cause)
|
|
{
|
|
uint8_t opcode, *data;
|
|
int len;
|
|
|
|
if (trans->spk) {
|
|
/* release after SPK assignment */
|
|
len = encode_aau(&opcode, &data, trans->spk_nr, 0, cnetz_cause2futln(cause));
|
|
message_send(trans->ident, opcode, data, len);
|
|
} else {
|
|
/* release before SPK assignment */
|
|
len = encode_nau(&opcode, &data, 0, cnetz_cause2futln(cause));
|
|
message_send(trans->ident, opcode, data, len);
|
|
}
|
|
|
|
if (callref)
|
|
call_up_release(callref, cause);
|
|
trans->callref = 0;
|
|
new_call_state(trans, STATE_RELEASE);
|
|
osmo_timer_schedule(&trans->timer, RELEASE_TO);
|
|
}
|
|
|
|
/* Call control sends disconnect (with tones).
|
|
* An active call stays active, so tones and annoucements can be received
|
|
* by mobile station.
|
|
*/
|
|
void call_down_disconnect(int callref, int cause)
|
|
{
|
|
transaction_t *trans;
|
|
|
|
LOGP(DCNETZ, LOGL_INFO, "Call has been disconnected by network.\n");
|
|
|
|
trans = search_transaction_callref(callref);
|
|
if (!trans) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Outgoing disconnect to unknown callref.\n");
|
|
call_up_release(callref, CAUSE_INVALCALLREF);
|
|
return;
|
|
}
|
|
|
|
/* already a channel, so we stay on to listen to disconnect tones */
|
|
if (trans->state != STATE_MT
|
|
&& trans->state != STATE_MT_QUEUE
|
|
&& trans->state != STATE_MT_ALERTING)
|
|
return;
|
|
|
|
_disconnect_release(trans, callref, cause);
|
|
|
|
}
|
|
|
|
/* Call control releases call toward mobile station. */
|
|
void call_down_release(int callref, int cause)
|
|
{
|
|
transaction_t *trans;
|
|
|
|
LOGP(DCNETZ, LOGL_INFO, "Call has been released by network.\n");
|
|
|
|
trans = search_transaction_callref(callref);
|
|
if (!trans) {
|
|
LOGP(DCNETZ, LOGL_NOTICE, "Outgoing released to unknown callref.\n");
|
|
call_up_release(callref, CAUSE_INVALCALLREF);
|
|
return;
|
|
}
|
|
|
|
_disconnect_release(trans, 0, cause);
|
|
}
|
|
|
|
void dump_info(void)
|
|
{
|
|
cnetz_db_t *db = cnetz_db_head;
|
|
|
|
LOGP(DDB, LOGL_NOTICE, "Dump of subscriber database:\n");
|
|
if (!db) {
|
|
LOGP(DDB, LOGL_NOTICE, " - No subscribers attached!\n");
|
|
return;
|
|
}
|
|
|
|
while (db) {
|
|
LOGP(DDB, LOGL_NOTICE, " - Subscriber '%d,%d,%d' is attached.\n", db->futln_nat, db->futln_fuvst, db->futln_rest);
|
|
db = db->next;
|
|
}
|
|
}
|
|
|