2003 lines
60 KiB
C
2003 lines
60 KiB
C
/* NMT protocol handling
|
|
*
|
|
* (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 nmt->sender.kanal
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include "../libsample/sample.h"
|
|
#include "../libdebug/debug.h"
|
|
#include "../libmncc/cause.h"
|
|
#include "nmt.h"
|
|
#include "transaction.h"
|
|
#include "dsp.h"
|
|
#include "frame.h"
|
|
#include "countries.h"
|
|
|
|
/* How does paging on all channels work:
|
|
*
|
|
* Paging is performed on all free CC channels. The transaction pointer
|
|
* is set towards transaction. (nmt->trans points to trans)
|
|
*
|
|
* After timeout, all channels with that transaction pointer are released.
|
|
*
|
|
* When paging was replied, other channels with the transaction pointer are
|
|
* relased, but the channel with the reply is linked to transaction in both
|
|
* directions. (trans->nmt points to nmt, nmt->trans points to trans)
|
|
*
|
|
*/
|
|
|
|
static int sms_ref = 0;
|
|
|
|
/* Call reference for calls from mobile station to network
|
|
This offset of 0x400000000 is required for MNCC interface. */
|
|
static int new_callref = 0x40000000;
|
|
|
|
/* Timers */
|
|
#define PAGING_TO 1.0 /* wait for paging response: fictive value */
|
|
#define RELEASE_TO 2.0 /* how long do we wait for release guard of the phone */
|
|
#define DIALING_TO 1.0 /* if we have a pause during dialing, we abort the call */
|
|
#define CHANNEL_TO 2.0 /* how long do we wait for phone to appear on assigned channel */
|
|
#define RINGING_TO 60.0 /* how long may the phone ring */
|
|
#define SUPERVISORY_TO1 3.0 /* 3 sec to detect after setup */
|
|
#define SUPERVISORY_TO2 20.0 /* 20 sec lost until abort */
|
|
|
|
/* Counters */
|
|
#define PAGE_TRIES 3 /* How many time do we try to page the phone */
|
|
|
|
const char *nmt_state_name(enum nmt_state state)
|
|
{
|
|
static char invalid[16];
|
|
|
|
switch (state) {
|
|
case STATE_NULL:
|
|
return "(NULL)";
|
|
case STATE_IDLE:
|
|
return "IDLE";
|
|
case STATE_ROAMING_IDENT:
|
|
return "ROAMING IDENT";
|
|
case STATE_ROAMING_CONFIRM:
|
|
return "ROAMING CONFIRM";
|
|
case STATE_MO_IDENT:
|
|
return "MO CALL IDENT";
|
|
case STATE_MO_CONFIRM:
|
|
return "MO CALL CONFIRM";
|
|
case STATE_MO_DIALING:
|
|
return "MO CALL DIALING";
|
|
case STATE_MO_COMPLETE:
|
|
return "MO CALL COMPLETE";
|
|
case STATE_MT_PAGING:
|
|
return "MT CALL PAGING";
|
|
case STATE_MT_CHANNEL:
|
|
return "MT CALL CHANNEL";
|
|
case STATE_MT_IDENT:
|
|
return "MT CALL IDENT";
|
|
case STATE_MT_AUTOANSWER:
|
|
return "MT CALL AUTOANSWER";
|
|
case STATE_MT_RINGING:
|
|
return "MT CALL RINGING";
|
|
case STATE_MT_COMPLETE:
|
|
return "MT CALL COMPLETE";
|
|
case STATE_ACTIVE:
|
|
return "ACTIVE";
|
|
case STATE_MO_RELEASE:
|
|
return "RELEASE MTX->MS";
|
|
case STATE_MT_RELEASE:
|
|
return "RELEASE MTX->MS";
|
|
}
|
|
|
|
sprintf(invalid, "invalid(%d)", state);
|
|
return invalid;
|
|
}
|
|
|
|
void nmt_display_status(void)
|
|
{
|
|
sender_t *sender;
|
|
nmt_t *nmt;
|
|
|
|
display_status_start();
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
nmt = (nmt_t *) sender;
|
|
display_status_channel(nmt->sender.kanal, chan_type_short_name(nmt->sysinfo.system, nmt->sysinfo.chan_type), nmt_state_name(nmt->state));
|
|
if (nmt->trans)
|
|
display_status_subscriber(nmt->trans->subscriber.number, NULL);
|
|
}
|
|
display_status_end();
|
|
}
|
|
|
|
static void nmt_new_state(nmt_t *nmt, enum nmt_state new_state)
|
|
{
|
|
if (nmt->state == new_state)
|
|
return;
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "State change: %s -> %s\n", nmt_state_name(nmt->state), nmt_state_name(new_state));
|
|
nmt->state = new_state;
|
|
nmt_display_status();
|
|
}
|
|
|
|
static struct nmt_channels {
|
|
int system;
|
|
enum nmt_chan_type chan_type;
|
|
const char *short_name;
|
|
const char *long_name;
|
|
} nmt_channels[] = {
|
|
{ 0, CHAN_TYPE_CC, "CC", "calling channel (incomming calls)" },
|
|
{ 900, CHAN_TYPE_CCA, "CCA", "calling channel for group A mobiles with odd secret key (incomming calls)" },
|
|
{ 900, CHAN_TYPE_CCB, "CCB", "calling channel for group B mobiles with even secret key (incomming calls)" },
|
|
{ 0, CHAN_TYPE_TC, "TC", "traffic channel (outgoing calls)" },
|
|
{ 900, CHAN_TYPE_AC_TC, "AC/TC","combined access & traffic channel (outgoing calls)" },
|
|
{ 0, CHAN_TYPE_CC_TC, "CC/TC","combined calling & traffic channel (both way calls)" },
|
|
{ 0, CHAN_TYPE_TEST, "TEST", "test channel" },
|
|
{ 0, 0, NULL, NULL }
|
|
};
|
|
|
|
void nmt_channel_list(int nmt_system)
|
|
{
|
|
int i;
|
|
|
|
printf("Type\tDescription\n");
|
|
printf("------------------------------------------------------------------------\n");
|
|
for (i = 0; nmt_channels[i].long_name; i++) {
|
|
if (nmt_channels[i].system != 0 && nmt_channels[i].system != nmt_system)
|
|
continue;
|
|
printf("%s\t%s\n", nmt_channels[i].short_name, nmt_channels[i].long_name);
|
|
}
|
|
}
|
|
|
|
int nmt_channel_by_short_name(int nmt_system, const char *short_name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; nmt_channels[i].short_name; i++) {
|
|
if (nmt_channels[i].system != 0 && nmt_channels[i].system != nmt_system)
|
|
continue;
|
|
if (!strcasecmp(nmt_channels[i].short_name, short_name))
|
|
return nmt_channels[i].chan_type;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
const char *chan_type_short_name(int nmt_system, enum nmt_chan_type chan_type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; nmt_channels[i].short_name; i++) {
|
|
if (nmt_channels[i].system != 0 && nmt_channels[i].system != nmt_system)
|
|
continue;
|
|
if (nmt_channels[i].chan_type == chan_type)
|
|
return nmt_channels[i].short_name;
|
|
}
|
|
|
|
return "invalid";
|
|
}
|
|
|
|
const char *chan_type_long_name(int nmt_system, enum nmt_chan_type chan_type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; nmt_channels[i].long_name; i++) {
|
|
if (nmt_channels[i].system != 0 && nmt_channels[i].system != nmt_system)
|
|
continue;
|
|
if (nmt_channels[i].chan_type == chan_type)
|
|
return nmt_channels[i].long_name;
|
|
}
|
|
|
|
return "invalid";
|
|
}
|
|
|
|
const char *nmt_dir_name(enum nmt_direction dir)
|
|
{
|
|
switch (dir) {
|
|
case MTX_TO_MS:
|
|
return "MTX->MS";
|
|
case MTX_TO_BS:
|
|
return "MTX->BS";
|
|
case MTX_TO_XX:
|
|
return "MTX->XX";
|
|
case BS_TO_MTX:
|
|
return "BS->MTX";
|
|
case MS_TO_MTX:
|
|
return "MS->MTX";
|
|
case XX_TO_MTX:
|
|
return "XX->MTX";
|
|
}
|
|
return "invalid";
|
|
}
|
|
|
|
/* convert 7-digits dial string to NMT number */
|
|
static int dialstring2number(const char *dialstring, char *ms_country, char *ms_number)
|
|
{
|
|
if (strlen(dialstring) != 7) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Wrong number of digits, use 7 digits: ZXXXXXX (Z=country, X=mobile number)\n");
|
|
return -1;
|
|
}
|
|
if (dialstring[0] < '0' || dialstring[0] > '9') {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Invalid country digit (first digit) of dial string\n");
|
|
return -1;
|
|
}
|
|
*ms_country = dialstring[0];
|
|
memcpy(ms_number, dialstring + 1, 6);
|
|
return 0;
|
|
}
|
|
|
|
static inline int is_chan_class_cc(enum nmt_chan_type chan_type)
|
|
{
|
|
if (chan_type == CHAN_TYPE_CC
|
|
|| chan_type == CHAN_TYPE_CCA
|
|
|| chan_type == CHAN_TYPE_CCB
|
|
|| chan_type == CHAN_TYPE_CC_TC)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int is_chan_class_tc(enum nmt_chan_type chan_type)
|
|
{
|
|
if (chan_type == CHAN_TYPE_TC
|
|
|| chan_type == CHAN_TYPE_AC_TC
|
|
|| chan_type == CHAN_TYPE_CC_TC)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nmt_timeout(struct timer *timer);
|
|
|
|
/* Create transceiver instance and link to a list. */
|
|
int nmt_create(int nmt_system, const char *country, int channel, enum nmt_chan_type chan_type, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, uint8_t ms_power, uint8_t traffic_area, uint8_t area_no, int compandor, int supervisory, const char *smsc_number, int send_callerid, int loopback)
|
|
{
|
|
nmt_t *nmt;
|
|
int rc;
|
|
double deviation_factor;
|
|
int scandinavia;
|
|
int tested;
|
|
|
|
/* check channel matching and set deviation factor */
|
|
if (nmt_channel2freq(nmt_system, country, channel, 0, &deviation_factor, &scandinavia, &tested) == 0.0) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Channel number %d invalid, use '-Y list' to get a list of available channels.\n", channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!tested) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** The given NMT country has not been tested yet. Please tell the Author, if it works.\n");
|
|
}
|
|
|
|
if (scandinavia && channel >= 201) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Channels numbers above 200 have been specified, but never used. These 'interleaved channels are probably not supports by the phone.\n");
|
|
}
|
|
|
|
if (scandinavia && channel >= 181 && channel <= 200) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Extended channel numbers (181..200) have been specified, but never been supported for sure. There is no phone to test with, so don't use it!\n");
|
|
}
|
|
|
|
if (chan_type == CHAN_TYPE_TEST && !loopback) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel can be used for nothing but testing signal decoder.\n");
|
|
}
|
|
|
|
nmt = calloc(1, sizeof(nmt_t));
|
|
if (!nmt) {
|
|
PDEBUG(DNMT, DEBUG_ERROR, "No memory!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
PDEBUG(DNMT, DEBUG_DEBUG, "Creating 'NMT' instance for channel = %d (sample rate %d).\n", channel, samplerate);
|
|
|
|
/* init general part of transceiver */
|
|
rc = sender_create(&nmt->sender, channel, nmt_channel2freq(nmt_system, country, channel, 0, NULL, NULL, NULL), nmt_channel2freq(nmt_system, country, channel, 1, NULL, NULL, NULL), audiodev, use_sdr, samplerate, rx_gain, pre_emphasis, de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE);
|
|
if (rc < 0) {
|
|
PDEBUG(DNMT, DEBUG_ERROR, "Failed to init transceiver process!\n");
|
|
goto error;
|
|
}
|
|
|
|
timer_init(&nmt->timer, nmt_timeout, nmt);
|
|
nmt->sysinfo.system = nmt_system;
|
|
nmt->sysinfo.chan_type = chan_type;
|
|
nmt->sysinfo.ms_power = ms_power;
|
|
nmt->sysinfo.traffic_area = traffic_area;
|
|
nmt->sysinfo.area_no = area_no;
|
|
nmt->compandor = compandor;
|
|
nmt->supervisory = supervisory;
|
|
nmt->send_callerid = send_callerid;
|
|
strncpy(nmt->smsc_number, smsc_number, sizeof(nmt->smsc_number) - 1);
|
|
|
|
/* init audio processing */
|
|
rc = dsp_init_sender(nmt, deviation_factor);
|
|
if (rc < 0) {
|
|
PDEBUG(DNMT, DEBUG_ERROR, "Failed to init audio processing!\n");
|
|
goto error;
|
|
}
|
|
|
|
/* init DMS processing */
|
|
rc = dms_init_sender(nmt);
|
|
if (rc < 0) {
|
|
PDEBUG(DNMT, DEBUG_ERROR, "Failed to init DMS processing!\n");
|
|
goto error;
|
|
}
|
|
|
|
/* init SMS processing */
|
|
rc = sms_init_sender(nmt);
|
|
if (rc < 0) {
|
|
PDEBUG(DNMT, DEBUG_ERROR, "Failed to init SMS processing!\n");
|
|
goto error;
|
|
}
|
|
|
|
/* go into idle state */
|
|
nmt_go_idle(nmt);
|
|
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Created channel #%d of type '%s' = %s\n", channel, chan_type_short_name(nmt_system, chan_type), chan_type_long_name(nmt_system, chan_type));
|
|
if (nmt_long_name_by_short_name(nmt_system, country))
|
|
PDEBUG(DNMT, DEBUG_NOTICE, " -> Using country '%s'\n", nmt_long_name_by_short_name(nmt_system, country));
|
|
PDEBUG(DNMT, DEBUG_NOTICE, " -> Using traffic area %d,%d and area no %d\n", traffic_area >> 4, traffic_area & 0xf, area_no);
|
|
if (nmt->supervisory)
|
|
PDEBUG(DNMT, DEBUG_NOTICE, " -> Using supervisory signal %d\n", supervisory);
|
|
else
|
|
PDEBUG(DNMT, DEBUG_NOTICE, " -> Using no supervisory signal\n");
|
|
|
|
return 0;
|
|
|
|
error:
|
|
nmt_destroy(&nmt->sender);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void nmt_check_channels(int __attribute__((unused)) nmt_system)
|
|
{
|
|
sender_t *sender;
|
|
nmt_t *nmt;
|
|
int cca = 0, ccb = 0, tc = 0;
|
|
int note = 0;
|
|
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
nmt = (nmt_t *) sender;
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_CC) {
|
|
cca = 1;
|
|
ccb = 1;
|
|
}
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_CCA)
|
|
cca = 1;
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_CCB)
|
|
ccb = 1;
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_TC)
|
|
tc = 1;
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_AC_TC)
|
|
tc = 1;
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_CC_TC) {
|
|
cca = 1;
|
|
ccb = 1;
|
|
tc = 1;
|
|
}
|
|
}
|
|
if ((cca || ccb) && !tc) {
|
|
if (note)
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel(s) can be used for calling only.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** No call is possible on this channel.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Use at least one 'TC'!\n");
|
|
note = 1;
|
|
}
|
|
if (tc && !(cca || ccb)) {
|
|
if (note)
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel(s) can be used for traffic only.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** No call to the mobile phone is possible on this channel.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Use one 'CC'!\n");
|
|
note = 1;
|
|
}
|
|
if (cca && !ccb) {
|
|
if (note)
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel(s) can be used for calling of MS type A only.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** No call to the MS type B phone is possible on this channel.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Use one 'CC' instead!\n");
|
|
note = 1;
|
|
}
|
|
if (!cca && ccb) {
|
|
if (note)
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel(s) can be used for calling of MS type B only.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** No call to the MS type A phone is possible on this channel.\n");
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "*** Use one 'CC' instead!\n");
|
|
note = 1;
|
|
}
|
|
}
|
|
|
|
/* Destroy transceiver instance and unlink from list. */
|
|
void nmt_destroy(sender_t *sender)
|
|
{
|
|
nmt_t *nmt = (nmt_t *) sender;
|
|
|
|
PDEBUG(DNMT, DEBUG_DEBUG, "Destroying 'NMT' instance for channel = %d.\n", sender->kanal);
|
|
dsp_cleanup_sender(nmt);
|
|
dms_cleanup_sender(nmt);
|
|
sms_cleanup_sender(nmt);
|
|
timer_exit(&nmt->timer);
|
|
sender_destroy(&nmt->sender);
|
|
free(nmt);
|
|
}
|
|
|
|
/* Abort connection towards mobile station by sending idle digits. */
|
|
void nmt_go_idle(nmt_t *nmt)
|
|
{
|
|
timer_stop(&nmt->timer);
|
|
dms_reset(nmt);
|
|
sms_reset(nmt);
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Entering IDLE state, sending idle frames on %s.\n", chan_type_long_name(nmt->sysinfo.system, nmt->sysinfo.chan_type));
|
|
nmt->trans = NULL; /* remove transaction before state change, so status is shown correctly */
|
|
nmt_new_state(nmt, STATE_IDLE);
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
memset(&nmt->dialing, 0, sizeof(nmt->dialing));
|
|
|
|
#if 0
|
|
/* go active for loopback tests */
|
|
nmt_new_state(nmt, STATE_ACTIVE);
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_AUDIO);
|
|
#endif
|
|
}
|
|
|
|
/* release an ongoing connection, this is used by roaming update and release initiated by MTX */
|
|
static void nmt_release(nmt_t *nmt)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
timer_stop(&nmt->timer);
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Releasing connection towards mobile station.\n");
|
|
if (trans->callref) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_ERROR, "Callref already set, please fix!\n");
|
|
abort();
|
|
}
|
|
nmt_new_state(nmt, STATE_MT_RELEASE);
|
|
nmt->tx_frame_count = 0;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
timer_start(&nmt->timer, RELEASE_TO);
|
|
}
|
|
|
|
/* Enter paging state and transmit phone's number on calling channel */
|
|
static void nmt_page(transaction_t *trans, int try)
|
|
{
|
|
sender_t *sender;
|
|
nmt_t *nmt;
|
|
|
|
PDEBUG(DNMT, DEBUG_INFO, "Entering paging state (try %d), sending call to '%c,%s'.\n", try, trans->subscriber.country, trans->subscriber.number);
|
|
trans->page_try = try;
|
|
timer_start(&trans->timer, PAGING_TO);
|
|
/* page on all CC (CC/TC) */
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
nmt = (nmt_t *)sender;
|
|
if (!is_chan_class_cc(nmt->sysinfo.chan_type))
|
|
continue;
|
|
/* page on all idle channels and on channels we previously paged */
|
|
if (nmt->state != STATE_IDLE && nmt->trans != trans)
|
|
continue;
|
|
PDEBUG(DNMT, DEBUG_INFO, "Paging on channel %d.\n", sender->kanal);
|
|
nmt->trans = trans; /* add transaction before state change, so status is shown correctly */
|
|
nmt_new_state(nmt, STATE_MT_PAGING);
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
nmt->tx_frame_count = 0;
|
|
}
|
|
}
|
|
|
|
static nmt_t *search_free_tc(nmt_t *own)
|
|
{
|
|
sender_t *sender;
|
|
nmt_t *nmt, *cc_tc = NULL;
|
|
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
nmt = (nmt_t *) sender;
|
|
/* if our CC is used, we don't care about busy state,
|
|
* because it can be used, if it is CC/TC type */
|
|
if (nmt != own && nmt->state != STATE_IDLE)
|
|
continue;
|
|
/* remember combined voice/control/paging channel as second alternative */
|
|
if (nmt->sysinfo.chan_type == CHAN_TYPE_CC_TC)
|
|
cc_tc = nmt;
|
|
else if (is_chan_class_tc(nmt->sysinfo.chan_type))
|
|
return nmt;
|
|
}
|
|
|
|
return cc_tc;
|
|
}
|
|
|
|
|
|
/*
|
|
* frame matching functions to check if channels is accessed correctly
|
|
*/
|
|
|
|
/* check match channel no, area no and traffic area */
|
|
static int match_channel(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
int channel, power;
|
|
int rc;
|
|
|
|
/* check channel match */
|
|
rc = nmt_decode_channel(nmt->sysinfo.system, frame->channel_no, &channel, &power);
|
|
if (rc < 0) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Frame with illegal encoded channel received, ignoring.\n");
|
|
return 0;
|
|
}
|
|
/* in case of interleaved channel, ignore the missing upper bit */
|
|
if ((channel % 1024) != (nmt->sender.kanal % 1024)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Frame for different channel %d received, ignoring.\n", channel);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int match_area(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
uint8_t area_no, traffic_area;
|
|
|
|
/* old phones do not support ZY digits */
|
|
if (frame->area_info + frame->traffic_area == 0)
|
|
goto skip_area;
|
|
|
|
area_no = frame->area_info >> 2;
|
|
if (area_no == 0 && nmt->sysinfo.area_no != 0)
|
|
area_no = 4;
|
|
if (area_no != nmt->sysinfo.area_no) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Received area no (%d) does not match the base station's area no (%d), ignoring.\n", area_no, nmt->sysinfo.area_no);
|
|
return 0;
|
|
}
|
|
|
|
traffic_area = ((frame->area_info & 0x3) << 4) | frame->traffic_area;
|
|
if (nmt->sysinfo.traffic_area != 0 && (nmt->sysinfo.traffic_area & 0x3f) != traffic_area) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Received 6 bits of traffic area (0x%02x) does not match the 6 bits of base station's traffic area (0x%02x), ignoring.\n", nmt->sysinfo.traffic_area & 0x3f, traffic_area);
|
|
return 0;
|
|
}
|
|
skip_area:
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* check match subscriber number */
|
|
static int match_subscriber(transaction_t *trans, frame_t *frame)
|
|
{
|
|
if (nmt_digits2value(&trans->subscriber.country, 1) != frame->ms_country) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Received non matching subscriber counrtry, ignoring.\n");
|
|
return 0;
|
|
}
|
|
if (nmt_digits2value(trans->subscriber.number, 6) != frame->ms_number) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Received non matching subscriber number, ignoring.\n");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* helper functions to generate frames
|
|
*/
|
|
|
|
static void tx_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
frame->mt = NMT_MESSAGE_3b;
|
|
frame->channel_no = nmt_encode_channel(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.ms_power);
|
|
frame->traffic_area = nmt_encode_traffic_area(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.traffic_area);
|
|
frame->ms_country = nmt_digits2value(&trans->subscriber.country, 1);
|
|
frame->ms_number = nmt_digits2value(trans->subscriber.number, 6);
|
|
frame->additional_info = nmt_encode_area_no(nmt->sysinfo.area_no);
|
|
}
|
|
|
|
static void set_line_signal(nmt_t *nmt, frame_t *frame, uint8_t signal)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
frame->mt = NMT_MESSAGE_5a;
|
|
frame->channel_no = nmt_encode_channel(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.ms_power);
|
|
frame->traffic_area = nmt_encode_traffic_area(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.traffic_area);
|
|
frame->ms_country = nmt_digits2value(&trans->subscriber.country, 1);
|
|
frame->ms_number = nmt_digits2value(trans->subscriber.number, 6);
|
|
frame->line_signal = (signal << 8) | (signal << 4) | signal;
|
|
}
|
|
|
|
/*
|
|
* handle idle channel
|
|
*/
|
|
|
|
static void tx_idle(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
switch (nmt->sysinfo.chan_type) {
|
|
case CHAN_TYPE_CC:
|
|
frame->mt = NMT_MESSAGE_1a;
|
|
break;
|
|
case CHAN_TYPE_CCA:
|
|
frame->mt = NMT_MESSAGE_1a_a;
|
|
break;
|
|
case CHAN_TYPE_CCB:
|
|
frame->mt = NMT_MESSAGE_1a_b;
|
|
break;
|
|
case CHAN_TYPE_TC:
|
|
frame->mt = NMT_MESSAGE_4;
|
|
break;
|
|
case CHAN_TYPE_AC_TC:
|
|
frame->mt = NMT_MESSAGE_4b;
|
|
break;
|
|
case CHAN_TYPE_CC_TC:
|
|
frame->mt = NMT_MESSAGE_1b;
|
|
break;
|
|
case CHAN_TYPE_TEST:
|
|
frame->mt = NMT_MESSAGE_30;
|
|
break;
|
|
}
|
|
frame->channel_no = nmt_encode_channel(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.ms_power);
|
|
frame->traffic_area = nmt_encode_traffic_area(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.traffic_area);
|
|
frame->additional_info = nmt_encode_area_no(nmt->sysinfo.area_no);
|
|
}
|
|
|
|
static void rx_idle(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans;
|
|
nmt_subscriber_t subscr;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_11a: /* roaming update and seizure */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
/* set subscriber */
|
|
memset(&subscr, 0, sizeof(subscr));
|
|
nmt_value2digits(frame->ms_country, &subscr.country, 1);
|
|
nmt_value2digits(frame->ms_number, subscr.number, 6);
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received roaming seizure from subscriber %c,%s\n", subscr.country, subscr.number);
|
|
|
|
/* create transaction */
|
|
trans = create_transaction(&subscr);
|
|
if (!trans) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Failed to create transaction!\n");
|
|
break;
|
|
}
|
|
|
|
/* change state */
|
|
nmt->trans = trans; /* add transaction before state change, so status is shown correctly */
|
|
nmt_new_state(nmt, STATE_ROAMING_IDENT);
|
|
trans->nmt = nmt;
|
|
nmt->rx_frame_count = 0;
|
|
nmt->tx_frame_count = 0;
|
|
break;
|
|
case NMT_MESSAGE_10b: /* seizure from ordinary MS */
|
|
case NMT_MESSAGE_12: /* seizure from coinbox MS */
|
|
case NMT_MESSAGE_10a: /* access signal */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
/* set subscriber */
|
|
memset(&subscr, 0, sizeof(subscr));
|
|
nmt_value2digits(frame->ms_country, &subscr.country, 1);
|
|
nmt_value2digits(frame->ms_number, subscr.number, 6);
|
|
if (frame->mt == NMT_MESSAGE_12)
|
|
subscr.coinbox = 1;
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received call from subscriber %c,%s%s\n", subscr.country, subscr.number, (subscr.coinbox) ? " (coinbox)" : "");
|
|
|
|
/* create transaction */
|
|
trans = create_transaction(&subscr);
|
|
if (!trans) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Failed to create transaction!\n");
|
|
break;
|
|
}
|
|
|
|
/* change state */
|
|
nmt->trans = trans; /* add transaction before state change, so status is shown correctly */
|
|
nmt_new_state(nmt, STATE_MO_IDENT);
|
|
trans->nmt = nmt;
|
|
nmt->rx_frame_count = 0;
|
|
nmt->tx_frame_count = 0;
|
|
break;
|
|
/* signals after release */
|
|
case NMT_MESSAGE_13a: /* line signal */
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* handle roaming
|
|
*/
|
|
|
|
static void tx_roaming_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Sending identity request.\n");
|
|
tx_ident(nmt, frame);
|
|
if (nmt->tx_frame_count == 8) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout waiting for identity reply\n");
|
|
nmt_release(nmt);
|
|
}
|
|
}
|
|
|
|
static void rx_roaming_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_11a: /* roaming update */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
if (nmt->rx_frame_count < 2) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Skipping second seizure frame\n");
|
|
break;
|
|
}
|
|
nmt_value2digits(frame->ms_password, trans->subscriber.password, 3);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received identity confirm (password %s).\n", trans->subscriber.password);
|
|
nmt_new_state(nmt, STATE_ROAMING_CONFIRM);
|
|
nmt->tx_frame_count = 0;
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_roaming_confirm(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
set_line_signal(nmt, frame, 3);
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'Roaming updating confirmation'.\n");
|
|
if (nmt->tx_frame_count == 2)
|
|
nmt_release(nmt); /* continue with this frame, then release */
|
|
}
|
|
|
|
static void rx_roaming_confirm(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
switch (frame->mt) {
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* handle call MS -> MTX
|
|
*/
|
|
|
|
static void tx_mo_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Sending identity request.\n");
|
|
tx_ident(nmt, frame);
|
|
if (nmt->tx_frame_count == 8) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout waiting for identity reply\n");
|
|
nmt_release(nmt);
|
|
}
|
|
}
|
|
|
|
static void rx_mo_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_10b: /* seizure */
|
|
case NMT_MESSAGE_12: /* seizure */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
if (nmt->rx_frame_count < 2) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Skipping second seizure frame\n");
|
|
break;
|
|
}
|
|
nmt_value2digits(frame->ms_password, trans->subscriber.password, 3);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received identity confirm (password %s).\n", trans->subscriber.password);
|
|
nmt_new_state(nmt, STATE_MO_CONFIRM);
|
|
nmt->tx_frame_count = 0;
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_mo_confirm(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
set_line_signal(nmt, frame, 3);
|
|
if (++nmt->tx_frame_count <= 2) {
|
|
if (nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'Proceed to send'.\n");
|
|
} else {
|
|
if (nmt->tx_frame_count == 3) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send dial tone.\n");
|
|
nmt_new_state(nmt, STATE_MO_DIALING);
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_DIALTONE);
|
|
timer_start(&nmt->timer, DIALING_TO);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rx_mo_dialing(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
int len = strlen(nmt->dialing);
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_14a: /* digits */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
timer_start(&nmt->timer, DIALING_TO);
|
|
/* max digits received */
|
|
if (len + 1 == sizeof(nmt->dialing))
|
|
break;
|
|
if ((len & 1)) {
|
|
/* received odd digit, but be already have odd number of digits */
|
|
if (nmt->rx_frame_count > 1) /* we lost even digit */
|
|
goto missing_digit;
|
|
break;
|
|
} else if (len) { /* complain only after first digit */
|
|
/* received odd digit, and we have even number of digits */
|
|
if (nmt->rx_frame_count > 3) /* we lost even digit */
|
|
goto missing_digit;
|
|
}
|
|
if ((frame->digit >> 12) != 0x00) /* digit 0x0 0x0, x, x, x */
|
|
goto not_right_position;
|
|
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|
|
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf))
|
|
goto not_consistent_digit;
|
|
nmt->dialing[len] = nmt_value2digit(frame->digit);
|
|
nmt->dialing[len + 1] = '\0';
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (odd) digit %c.\n", nmt->dialing[len]);
|
|
nmt->rx_frame_count = 0;
|
|
/* finish dial tone after first digit */
|
|
if (!len)
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_AUDIO);
|
|
break;
|
|
case NMT_MESSAGE_14b: /* digits */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
timer_start(&nmt->timer, DIALING_TO);
|
|
/* max digits received */
|
|
if (len + 1 == sizeof(nmt->dialing))
|
|
break;
|
|
/* received even digit, but no digit yet, so we lost first odd digit */
|
|
if (!len)
|
|
goto missing_digit;
|
|
if (!(len & 1)) {
|
|
/* received even digit, but be already have even number of digits */
|
|
if (nmt->rx_frame_count > 1) /* we lost odd digit */
|
|
goto missing_digit;
|
|
break;
|
|
} else {
|
|
/* received even digit, and we have odd number of digits */
|
|
if (nmt->rx_frame_count > 3) /* we lost odd digit */
|
|
goto missing_digit;
|
|
}
|
|
if ((frame->digit >> 12) != 0xff) /* digit 0xf 0xf, x, x, x */
|
|
goto not_right_position;
|
|
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|
|
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf))
|
|
goto not_consistent_digit;
|
|
nmt->dialing[len] = nmt_value2digit(frame->digit);
|
|
nmt->dialing[len + 1] = '\0';
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (even) digit %c.\n", nmt->dialing[len]);
|
|
nmt->rx_frame_count = 0;
|
|
break;
|
|
case NMT_MESSAGE_15: /* idle */
|
|
if (!len)
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Dialing complete %s->%s, call established.\n", &trans->subscriber.country, nmt->dialing);
|
|
/* setup call */
|
|
if (!strcmp(nmt->dialing, nmt->smsc_number)) {
|
|
/* SMS */
|
|
PDEBUG(DNMT, DEBUG_INFO, "Setup call to SMSC.\n");
|
|
trans->dms_call = 1;
|
|
} else {
|
|
int callref = ++new_callref;
|
|
int rc;
|
|
PDEBUG(DNMT, DEBUG_INFO, "Setup call to network.\n");
|
|
rc = call_up_setup(callref, &trans->subscriber.country, nmt->dialing);
|
|
if (rc < 0) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", rc);
|
|
nmt_release(nmt);
|
|
return;
|
|
}
|
|
trans->callref = callref;
|
|
}
|
|
timer_stop(&nmt->timer);
|
|
nmt_new_state(nmt, STATE_MO_COMPLETE);
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
nmt->tx_frame_count = 0;
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
|
|
return;
|
|
|
|
missing_digit:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Missing digit, aborting.\n");
|
|
nmt_release(nmt);
|
|
return;
|
|
|
|
not_right_position:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Position information of digit does not match, ignoring due to corrupt frame.\n");
|
|
return;
|
|
|
|
not_consistent_digit:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Digit repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
return;
|
|
}
|
|
|
|
static void tx_mo_complete(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
if (++nmt->tx_frame_count <= 4) {
|
|
set_line_signal(nmt, frame, 6);
|
|
if (nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'address complete'.\n");
|
|
} else {
|
|
if (nmt->compandor) {
|
|
set_line_signal(nmt, frame, 5);
|
|
if (nmt->tx_frame_count == 5)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'compandor in'.\n");
|
|
} else
|
|
frame->mt = NMT_MESSAGE_6;
|
|
if (nmt->tx_frame_count == 9) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Connect audio.\n");
|
|
nmt_new_state(nmt, STATE_ACTIVE);
|
|
nmt->active_state = ACTIVE_STATE_VOICE;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_AUDIO);
|
|
if (nmt->supervisory && !trans->dms_call) {
|
|
super_reset(nmt);
|
|
timer_start(&nmt->timer, SUPERVISORY_TO1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void timeout_mo_dialing(nmt_t *nmt)
|
|
{
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout while receiving digits.\n");
|
|
nmt_release(nmt);
|
|
PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
}
|
|
|
|
/*
|
|
* handle call MTX -> MS
|
|
*/
|
|
static void tx_mt_paging(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
frame->mt = NMT_MESSAGE_2a;
|
|
frame->channel_no = nmt_encode_channel(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.ms_power);
|
|
frame->traffic_area = nmt_encode_traffic_area(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.traffic_area);
|
|
frame->ms_country = nmt_digits2value(&trans->subscriber.country, 1);
|
|
frame->ms_number = nmt_digits2value(trans->subscriber.number, 6);
|
|
frame->additional_info = nmt_encode_area_no(nmt->sysinfo.area_no);
|
|
if (++nmt->tx_frame_count == 1) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send call to mobile.\n");
|
|
} else
|
|
tx_idle(nmt, frame);
|
|
}
|
|
|
|
void timeout_mt_paging(transaction_t *trans)
|
|
{
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "No answer from mobile phone (try %d).\n", trans->page_try);
|
|
if (trans->page_try == PAGE_TRIES) {
|
|
PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
call_up_release(trans->callref, CAUSE_OUTOFORDER);
|
|
destroy_transaction(trans);
|
|
return;
|
|
}
|
|
nmt_page(trans, trans->page_try + 1);
|
|
}
|
|
|
|
static void rx_mt_paging(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
sender_t *sender;
|
|
nmt_t *other;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_10a: /* call acknowledgment */
|
|
case NMT_MESSAGE_10d: /* call ack on alternate type */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received call acknowledgment from subscriber %c,%s.\n", trans->subscriber.country, trans->subscriber.number);
|
|
if (trans->sms_string[0])
|
|
trans->dms_call = 1;
|
|
timer_stop(&trans->timer);
|
|
nmt_new_state(nmt, STATE_MT_CHANNEL);
|
|
trans->nmt = nmt;
|
|
nmt->tx_frame_count = 0;
|
|
/* release other channels */
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
other = (nmt_t *)sender;
|
|
if (other == nmt)
|
|
continue;
|
|
if (other->trans == trans)
|
|
nmt_go_idle(other);
|
|
}
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_mt_channel(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
nmt_t *tc;
|
|
|
|
/* get free channel (after releasing all channels) */
|
|
tc = search_free_tc(nmt);
|
|
if (!tc) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "TC is not free anymore.\n");
|
|
PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
call_up_release(trans->callref, CAUSE_NOCHANNEL);
|
|
trans->callref = 0;
|
|
nmt_release(nmt);
|
|
/* send idle for now, then continue with release */
|
|
tx_idle(nmt, frame);
|
|
return;
|
|
}
|
|
|
|
if (nmt != tc) {
|
|
/* link trans and tc together, so we can continue with channel assignment */
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Switching to TC channel #%d.\n", tc->sender.kanal);
|
|
nmt_go_idle(nmt);
|
|
tc->trans = trans;
|
|
trans->nmt = tc;
|
|
} else
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Staying on CC/TC channel #%d.\n", tc->sender.kanal);
|
|
nmt_new_state(tc, STATE_MT_IDENT);
|
|
|
|
/* assign channel on 'nmt' to 'tc' */
|
|
frame->mt = NMT_MESSAGE_2b;
|
|
frame->channel_no = nmt_encode_channel(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.ms_power);
|
|
frame->traffic_area = nmt_encode_traffic_area(nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.traffic_area);
|
|
frame->ms_country = nmt_digits2value(&trans->subscriber.country, 1);
|
|
frame->ms_number = nmt_digits2value(trans->subscriber.number, 6);
|
|
frame->tc_no = nmt_encode_tc(tc->sysinfo.system, tc->sender.kanal, tc->sysinfo.ms_power);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send channel activation to mobile.\n");
|
|
}
|
|
|
|
static void tx_mt_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Sending identity request.\n");
|
|
tx_ident(nmt, frame);
|
|
if (nmt->tx_frame_count == 8) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout waiting for identity reply\n");
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
call_up_release(trans->callref, CAUSE_TEMPFAIL);
|
|
destroy_transaction(trans);
|
|
}
|
|
}
|
|
|
|
static void rx_mt_ident(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_10b: /* seizure */
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
nmt_value2digits(frame->ms_password, trans->subscriber.password, 3);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received identity (password %s).\n", trans->subscriber.password);
|
|
if (trans->dms_call) {
|
|
nmt_new_state(nmt, STATE_MT_AUTOANSWER);
|
|
nmt->wait_autoanswer = 1;
|
|
nmt->tx_frame_count = 0;
|
|
} else {
|
|
nmt_new_state(nmt, STATE_MT_RINGING);
|
|
/* start with caller ID before ringing */
|
|
if (nmt->send_callerid) {
|
|
nmt->tx_frame_count = 4;
|
|
nmt->tx_callerid_count = 1;
|
|
} else {
|
|
nmt->tx_frame_count = 0;
|
|
nmt->tx_callerid_count = 0;
|
|
}
|
|
timer_start(&nmt->timer, RINGING_TO);
|
|
call_up_alerting(trans->callref);
|
|
}
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_mt_autoanswer(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
/* first we need to wait for autoanswer */
|
|
if (nmt->wait_autoanswer) {
|
|
frame->mt = NMT_MESSAGE_6;
|
|
return;
|
|
}
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'autoanswer order'.\n");
|
|
set_line_signal(nmt, frame, 12);
|
|
if (nmt->tx_frame_count == 4) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "No reaction to autoanswer, proceed with ringing.\n");
|
|
nmt_new_state(nmt, STATE_MT_RINGING);
|
|
nmt->tx_frame_count = 0;
|
|
nmt->tx_callerid_count = 0;
|
|
timer_start(&nmt->timer, RINGING_TO);
|
|
call_up_alerting(trans->callref);
|
|
}
|
|
}
|
|
|
|
static void rx_mt_autoanswer(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_15: /* idle */
|
|
nmt->wait_autoanswer = 0;
|
|
break;
|
|
case NMT_MESSAGE_13a: /* line signal */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|
|
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|
|
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|
|
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
if ((frame->line_signal & 0xf) != 12)
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received acknowledge to autoanswer.\n");
|
|
nmt_new_state(nmt, STATE_MT_COMPLETE);
|
|
nmt->tx_frame_count = 0;
|
|
call_up_answer(trans->callref, &trans->subscriber.country);
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_mt_ringing(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
set_line_signal(nmt, frame, 9);
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'ringing order'.\n");
|
|
if (nmt->tx_frame_count >= 4) {
|
|
if (nmt->tx_callerid_count) {
|
|
if (nmt->tx_frame_count == 5)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'A-number'.\n");
|
|
nmt_encode_a_number(frame, nmt->tx_frame_count - 4, trans->caller_type, trans->caller_id, nmt->sysinfo.system, nmt->sender.kanal, nmt->sysinfo.ms_power, nmt->sysinfo.traffic_area);
|
|
} else
|
|
frame->mt = NMT_MESSAGE_6;
|
|
}
|
|
if (nmt->tx_callerid_count == 1) {
|
|
/* start ringing after first caller ID of 6 frames */
|
|
if (nmt->tx_frame_count == 10) {
|
|
nmt->tx_frame_count = 0;
|
|
nmt->tx_callerid_count++;
|
|
}
|
|
} else {
|
|
/* repeat ringing after 5 seconds */
|
|
if (nmt->tx_frame_count == 36) {
|
|
nmt->tx_frame_count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rx_mt_ringing(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_13a: /* line signal */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|
|
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|
|
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|
|
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
if ((frame->line_signal & 0xf) != 14)
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received 'answer' from phone.\n");
|
|
nmt_new_state(nmt, STATE_MT_COMPLETE);
|
|
nmt->tx_frame_count = 0;
|
|
timer_stop(&nmt->timer);
|
|
call_up_answer(trans->callref, &trans->subscriber.country);
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_mt_complete(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
++nmt->tx_frame_count;
|
|
if (nmt->compandor && !trans->dms_call) {
|
|
if (nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'compandor in'.\n");
|
|
set_line_signal(nmt, frame, 5);
|
|
} else
|
|
frame->mt = NMT_MESSAGE_6;
|
|
if (nmt->tx_frame_count == 5) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Connect audio.\n");
|
|
nmt_new_state(nmt, STATE_ACTIVE);
|
|
nmt->active_state = ACTIVE_STATE_VOICE;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_AUDIO);
|
|
if (nmt->supervisory && !trans->dms_call) {
|
|
super_reset(nmt);
|
|
timer_start(&nmt->timer, SUPERVISORY_TO1);
|
|
}
|
|
if (trans->dms_call) {
|
|
time_t ti = time(NULL);
|
|
sms_deliver(nmt, sms_ref, trans->caller_id, trans->caller_type, SMS_PLAN_ISDN_TEL, ti, trans->sms_string);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void timeout_mt_ringing(nmt_t *nmt)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout while waiting for answer of the phone.\n");
|
|
PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
call_up_release(trans->callref, CAUSE_NOANSWER);
|
|
trans->callref = 0;
|
|
nmt_release(nmt);
|
|
}
|
|
|
|
/*
|
|
* handle clearing towards MTX
|
|
*/
|
|
|
|
static void tx_mo_release(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
set_line_signal(nmt, frame, 15);
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send release.\n");
|
|
if (nmt->tx_frame_count == 4)
|
|
destroy_transaction(trans); /* continue with this frame, then go idle */
|
|
}
|
|
|
|
/*
|
|
* handle clearing towards MS
|
|
*/
|
|
|
|
static void tx_mt_release(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
set_line_signal(nmt, frame, 15);
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send release.\n");
|
|
}
|
|
|
|
static void rx_mt_release(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_13a: /* line signal */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|
|
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|
|
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|
|
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
if ((frame->line_signal & 0xf) != 1)
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received release guard.\n");
|
|
timer_stop(&nmt->timer);
|
|
destroy_transaction(trans);
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void timeout_mt_release(nmt_t *nmt)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout while releasing.\n");
|
|
destroy_transaction(trans);
|
|
}
|
|
|
|
/*
|
|
* handle call
|
|
*/
|
|
|
|
void nmt_rx_super(nmt_t *nmt, int tone, double quality)
|
|
{
|
|
if (tone)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Detected supervisory signal with quality=%.0f.\n", quality * 100.0);
|
|
else
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Lost supervisory signal\n");
|
|
|
|
if (nmt->sender.loopback)
|
|
return;
|
|
|
|
/* only detect supervisory signal during active call */
|
|
if (nmt->state != STATE_ACTIVE || !nmt->supervisory)
|
|
return;
|
|
|
|
if (tone)
|
|
timer_stop(&nmt->timer);
|
|
else
|
|
timer_start(&nmt->timer, SUPERVISORY_TO2);
|
|
}
|
|
|
|
static void timeout_active(nmt_t *nmt, double duration)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
|
|
if (duration == SUPERVISORY_TO1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout after %.0f seconds not receiving supervisory signal.\n", duration);
|
|
else
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Timeout after %.0f seconds loosing supervisory signal.\n", duration);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
call_up_release(trans->callref, CAUSE_TEMPFAIL);
|
|
trans->callref = 0;
|
|
nmt_release(nmt);
|
|
}
|
|
|
|
static void rx_active(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
transaction_t *trans = nmt->trans;
|
|
char digit;
|
|
|
|
/* restart timer on every reception of frame */
|
|
if (nmt->supervisory)
|
|
timer_start(&nmt->timer, SUPERVISORY_TO2);
|
|
|
|
switch (frame->mt) {
|
|
case NMT_MESSAGE_13a: /* line signal */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_subscriber(trans, frame))
|
|
break;
|
|
if (((frame->line_signal >> 16) & 0xf) != ((frame->line_signal >> 12) & 0xf)
|
|
|| ((frame->line_signal >> 12) & 0xf) != ((frame->line_signal >> 8) & 0xf)
|
|
|| ((frame->line_signal >> 8) & 0xf) != ((frame->line_signal >> 4) & 0xf)
|
|
|| ((frame->line_signal >> 4) & 0xf) != (frame->line_signal & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
switch ((frame->line_signal & 0xf)) {
|
|
case 5:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Register Recall is not supported.\n");
|
|
break;
|
|
case 8:
|
|
if (nmt->active_state != ACTIVE_STATE_VOICE)
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received 'MFT in' request.\n");
|
|
nmt->active_state = ACTIVE_STATE_MFT_IN;
|
|
nmt->tx_frame_count = 0;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
nmt->mft_num = 0;
|
|
break;
|
|
case 7:
|
|
if (nmt->active_state != ACTIVE_STATE_MFT)
|
|
break;
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received 'MFT out' request.\n");
|
|
nmt->active_state = ACTIVE_STATE_MFT_OUT;
|
|
nmt->tx_frame_count = 0;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
break;
|
|
}
|
|
break;
|
|
case NMT_MESSAGE_14a: /* digits */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
if (nmt->active_state != ACTIVE_STATE_MFT)
|
|
break;
|
|
if ((nmt->mft_num & 1))
|
|
break;
|
|
if ((frame->digit >> 12) != 0x00) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Position information of digit does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|
|
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Digit repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
digit = nmt_value2digit(frame->digit);
|
|
dtmf_encode_set_tone(&nmt->dtmf, digit);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (odd) digit %c.\n", digit);
|
|
nmt->mft_num++;
|
|
break;
|
|
case NMT_MESSAGE_14b: /* digits */
|
|
if (!match_channel(nmt, frame))
|
|
break;
|
|
if (!match_area(nmt, frame))
|
|
break;
|
|
if (nmt->active_state != ACTIVE_STATE_MFT)
|
|
break;
|
|
if (!(nmt->mft_num & 1))
|
|
break;
|
|
if ((frame->digit >> 12) != 0xff) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Position information of digit does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
if (((frame->digit >> 8) & 0xf) != ((frame->digit >> 4) & 0xf)
|
|
|| ((frame->digit >> 4) & 0xf) != (frame->digit & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Digit repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
break;
|
|
}
|
|
digit = nmt_value2digit(frame->digit);
|
|
dtmf_encode_set_tone(&nmt->dtmf, digit);
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received (even) digit %c.\n", digit);
|
|
nmt->mft_num++;
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
static void tx_active(nmt_t *nmt, frame_t *frame)
|
|
{
|
|
switch (nmt->active_state) {
|
|
case ACTIVE_STATE_MFT_IN:
|
|
set_line_signal(nmt, frame, 4);
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'MFT in acknowledge'.\n");
|
|
if (nmt->tx_frame_count > 4) {
|
|
nmt->active_state = ACTIVE_STATE_MFT;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_DTMF);
|
|
}
|
|
break;
|
|
case ACTIVE_STATE_MFT_OUT:
|
|
set_line_signal(nmt, frame, 10);
|
|
if (++nmt->tx_frame_count == 1)
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Send 'MFT out acknowledge'.\n");
|
|
if (nmt->tx_frame_count > 4) {
|
|
nmt->active_state = ACTIVE_STATE_VOICE;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_AUDIO);
|
|
if (nmt->supervisory)
|
|
super_reset(nmt);
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* general handlers to call sub handling
|
|
*/
|
|
|
|
void nmt_receive_frame(nmt_t *nmt, const char *bits, double quality, double level, int frames_elapsed)
|
|
{
|
|
frame_t frame;
|
|
int rc;
|
|
|
|
PDEBUG_CHAN(DDSP, DEBUG_INFO, "RX Level: %.0f%% Quality=%.0f%%\n", level * 100.0, quality * 100.0);
|
|
|
|
rc = decode_frame(nmt->sysinfo.system, &frame, bits, (nmt->sender.loopback) ? MTX_TO_XX : XX_TO_MTX, (nmt->state == STATE_MT_PAGING));
|
|
if (rc < 0) {
|
|
PDEBUG_CHAN(DNMT, (nmt->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received invalid frame.\n");
|
|
return;
|
|
}
|
|
|
|
/* frame counter */
|
|
nmt->rx_frame_count += frames_elapsed;
|
|
|
|
PDEBUG_CHAN(DNMT, (nmt->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received frame %s\n", nmt_frame_name(frame.mt));
|
|
|
|
if (nmt->sender.loopback)
|
|
return;
|
|
|
|
/* MS releases, but this is not the acknowledge of MTX release */
|
|
if (frame.mt == NMT_MESSAGE_13a
|
|
&& (frame.line_signal & 0xf) == 1
|
|
&& nmt->state != STATE_MO_RELEASE
|
|
&& nmt->state != STATE_MT_RELEASE) {
|
|
/* drop packets after release */
|
|
if (nmt->state == STATE_IDLE)
|
|
return;
|
|
if (!match_subscriber(nmt->trans, &frame))
|
|
return;
|
|
if (((frame.line_signal >> 16) & 0xf) != ((frame.line_signal >> 12) & 0xf)
|
|
|| ((frame.line_signal >> 12) & 0xf) != ((frame.line_signal >> 8) & 0xf)
|
|
|| ((frame.line_signal >> 8) & 0xf) != ((frame.line_signal >> 4) & 0xf)
|
|
|| ((frame.line_signal >> 4) & 0xf) != (frame.line_signal & 0xf)) {
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Line signal repetition in frame does not match, ignoring due to corrupt frame.\n");
|
|
return;
|
|
}
|
|
PDEBUG_CHAN(DNMT, DEBUG_INFO, "Received clearing by mobile phone in state %s.\n", nmt_state_name(nmt->state));
|
|
nmt_new_state(nmt, STATE_MO_RELEASE);
|
|
nmt->tx_frame_count = 0;
|
|
nmt_set_dsp_mode(nmt, DSP_MODE_FRAME);
|
|
if (nmt->trans->callref) {
|
|
PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n");
|
|
call_up_release(nmt->trans->callref, CAUSE_NORMAL);
|
|
nmt->trans->callref = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (nmt->state) {
|
|
case STATE_IDLE:
|
|
rx_idle(nmt, &frame);
|
|
break;
|
|
case STATE_ROAMING_IDENT:
|
|
rx_roaming_ident(nmt, &frame);
|
|
break;
|
|
case STATE_ROAMING_CONFIRM:
|
|
rx_roaming_confirm(nmt, &frame);
|
|
break;
|
|
case STATE_MO_IDENT:
|
|
rx_mo_ident(nmt, &frame);
|
|
break;
|
|
case STATE_MO_CONFIRM:
|
|
case STATE_MO_DIALING:
|
|
rx_mo_dialing(nmt, &frame);
|
|
break;
|
|
case STATE_MO_RELEASE:
|
|
break;
|
|
case STATE_MT_PAGING:
|
|
rx_mt_paging(nmt, &frame);
|
|
break;
|
|
case STATE_MT_IDENT:
|
|
rx_mt_ident(nmt, &frame);
|
|
break;
|
|
case STATE_MT_AUTOANSWER:
|
|
rx_mt_autoanswer(nmt, &frame);
|
|
break;
|
|
case STATE_MT_RINGING:
|
|
rx_mt_ringing(nmt, &frame);
|
|
break;
|
|
case STATE_MT_RELEASE:
|
|
rx_mt_release(nmt, &frame);
|
|
break;
|
|
case STATE_ACTIVE:
|
|
rx_active(nmt, &frame);
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame.mt), nmt_state_name(nmt->state));
|
|
}
|
|
}
|
|
|
|
/* Timeout handling */
|
|
static void nmt_timeout(struct timer *timer)
|
|
{
|
|
nmt_t *nmt = (nmt_t *)timer->priv;
|
|
|
|
switch (nmt->state) {
|
|
case STATE_MO_DIALING:
|
|
timeout_mo_dialing(nmt);
|
|
break;
|
|
case STATE_MT_RINGING:
|
|
timeout_mt_ringing(nmt);
|
|
break;
|
|
case STATE_MT_RELEASE:
|
|
timeout_mt_release(nmt);
|
|
break;
|
|
case STATE_ACTIVE:
|
|
timeout_active(nmt, timer->duration);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* FSK processing requests next frame after transmission of previous
|
|
frame has been finished. */
|
|
const char *nmt_get_frame(nmt_t *nmt)
|
|
{
|
|
frame_t frame;
|
|
const char *bits;
|
|
int debug = 1;
|
|
|
|
memset(&frame, 0, sizeof(frame));
|
|
|
|
switch(nmt->state) {
|
|
case STATE_IDLE:
|
|
tx_idle(nmt, &frame);
|
|
break;
|
|
case STATE_ROAMING_IDENT:
|
|
tx_roaming_ident(nmt, &frame);
|
|
break;
|
|
case STATE_ROAMING_CONFIRM:
|
|
tx_roaming_confirm(nmt, &frame);
|
|
break;
|
|
case STATE_MO_IDENT:
|
|
tx_mo_ident(nmt, &frame);
|
|
break;
|
|
case STATE_MO_CONFIRM:
|
|
tx_mo_confirm(nmt, &frame);
|
|
break;
|
|
case STATE_MO_COMPLETE:
|
|
tx_mo_complete(nmt, &frame);
|
|
break;
|
|
case STATE_MO_RELEASE:
|
|
tx_mo_release(nmt, &frame);
|
|
break;
|
|
case STATE_MT_PAGING:
|
|
tx_mt_paging(nmt, &frame);
|
|
break;
|
|
case STATE_MT_CHANNEL:
|
|
tx_mt_channel(nmt, &frame);
|
|
break;
|
|
case STATE_MT_IDENT:
|
|
tx_mt_ident(nmt, &frame);
|
|
break;
|
|
case STATE_MT_AUTOANSWER:
|
|
tx_mt_autoanswer(nmt, &frame);
|
|
break;
|
|
case STATE_MT_RINGING:
|
|
tx_mt_ringing(nmt, &frame);
|
|
break;
|
|
case STATE_MT_COMPLETE:
|
|
tx_mt_complete(nmt, &frame);
|
|
break;
|
|
case STATE_MT_RELEASE:
|
|
tx_mt_release(nmt, &frame);
|
|
break;
|
|
case STATE_ACTIVE:
|
|
tx_active(nmt, &frame);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* no encoding debug for certain (idle) frames */
|
|
switch(frame.mt) {
|
|
case NMT_MESSAGE_1a:
|
|
case NMT_MESSAGE_4:
|
|
case NMT_MESSAGE_1b:
|
|
case NMT_MESSAGE_30:
|
|
debug = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* frame sending aborted (e.g. due to audio) */
|
|
if (nmt->dsp_mode != DSP_MODE_FRAME)
|
|
return NULL;
|
|
|
|
bits = encode_frame(nmt->sysinfo.system, &frame, debug);
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_DEBUG, "Sending frame %s.\n", nmt_frame_name(frame.mt));
|
|
return bits;
|
|
}
|
|
|
|
/*
|
|
* call states received from call control
|
|
*/
|
|
|
|
/* Call control starts call towards mobile station. */
|
|
int _out_setup(int callref, const char *caller_id, enum number_type caller_type, const char *dialing, const char *sms)
|
|
{
|
|
sender_t *sender;
|
|
nmt_t *nmt;
|
|
int i;
|
|
nmt_subscriber_t subscr;
|
|
transaction_t *trans;
|
|
|
|
memset(&subscr, 0, sizeof(subscr));
|
|
|
|
/* 1. check if number is invalid, return INVALNUMBER */
|
|
if (dialstring2number(dialing, &subscr.country, subscr.number)) {
|
|
inval:
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
|
|
return -CAUSE_INVALNUMBER;
|
|
}
|
|
for (i = 0; i < 6; i++) {
|
|
if (subscr.number[i] < '0' || subscr.number[i] > '9')
|
|
goto inval;
|
|
}
|
|
|
|
/* 2. check if given number is already in a call, return BUSY */
|
|
trans = get_transaction_by_number(&subscr);
|
|
if (trans) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
|
|
return -CAUSE_BUSY;
|
|
}
|
|
|
|
/* 3. check if all paging (calling) channels are busy, return NOCHANNEL */
|
|
for (sender = sender_head; sender; sender = sender->next) {
|
|
nmt = (nmt_t *) sender;
|
|
if (!is_chan_class_cc(nmt->sysinfo.chan_type))
|
|
continue;
|
|
if (nmt->state == STATE_IDLE)
|
|
break;
|
|
}
|
|
if (!sender) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing call, but no free calling channel, rejecting!\n");
|
|
return -CAUSE_NOCHANNEL;
|
|
}
|
|
if (!search_free_tc(NULL)) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing call, but no free traffic channel, rejecting!\n");
|
|
return -CAUSE_NOCHANNEL;
|
|
}
|
|
|
|
PDEBUG(DNMT, DEBUG_INFO, "Call to mobile station, paging station id '%c%s'\n", subscr.country, subscr.number);
|
|
|
|
/* 4. trying to page mobile station */
|
|
trans = create_transaction(&subscr);
|
|
if (!trans) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Failed to create transaction, rejecting!\n");
|
|
return -CAUSE_TEMPFAIL;
|
|
}
|
|
trans->callref = callref;
|
|
if (sms) {
|
|
strncpy(trans->sms_string, sms, sizeof(trans->sms_string) - 1);
|
|
}
|
|
if (caller_type == TYPE_INTERNATIONAL) {
|
|
trans->caller_id[0] = '+'; /* not done by phone */
|
|
strncpy(trans->caller_id + 1, caller_id, sizeof(trans->caller_id) - 2);
|
|
} else
|
|
strncpy(trans->caller_id, caller_id, sizeof(trans->caller_id) - 1);
|
|
trans->caller_type = caller_type;
|
|
nmt_page(trans, 1);
|
|
|
|
return 0;
|
|
}
|
|
int call_down_setup(int callref, const char *caller_id, enum number_type caller_type, const char *dialing)
|
|
{
|
|
return _out_setup(callref, caller_id, caller_type, dialing, NULL);
|
|
}
|
|
int sms_out_setup(char *dialing, const char *caller_id, enum number_type caller_type, const char *sms)
|
|
{
|
|
return _out_setup(0, caller_id, caller_type, dialing, sms);
|
|
}
|
|
|
|
void call_down_answer(int __attribute__((unused)) callref)
|
|
{
|
|
}
|
|
|
|
/* 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;
|
|
nmt_t *nmt;
|
|
|
|
PDEBUG(DNMT, DEBUG_INFO, "Call has been disconnected by network.\n");
|
|
|
|
trans = get_transaction_by_callref(callref);
|
|
if (!trans) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
|
|
call_up_release(callref, CAUSE_INVALCALLREF);
|
|
return;
|
|
}
|
|
nmt = trans->nmt;
|
|
|
|
if (!nmt) {
|
|
call_up_release(callref, cause);
|
|
trans->callref = 0;
|
|
destroy_transaction(trans);
|
|
return;
|
|
}
|
|
|
|
/* Release when not active and not waiting for answer */
|
|
if (nmt->state == STATE_ACTIVE || nmt->state == STATE_MO_COMPLETE)
|
|
return;
|
|
switch (nmt->state) {
|
|
case STATE_MT_RINGING:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Outgoing disconnect, during ringing, releasing!\n");
|
|
trans->callref = 0;
|
|
nmt_release(nmt);
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Outgoing disconnect, when phone is in call setup, releasing!\n");
|
|
trans->callref = 0;
|
|
nmt_release(nmt);
|
|
break;
|
|
}
|
|
|
|
call_up_release(callref, cause);
|
|
}
|
|
|
|
/* Call control releases call toward mobile station. */
|
|
void call_down_release(int callref, int __attribute__((unused)) cause)
|
|
{
|
|
transaction_t *trans;
|
|
nmt_t *nmt;
|
|
|
|
PDEBUG(DNMT, DEBUG_INFO, "Call has been released by network, releasing call.\n");
|
|
|
|
trans = get_transaction_by_callref(callref);
|
|
if (!trans) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
|
|
/* don't send release, because caller already released */
|
|
return;
|
|
}
|
|
nmt = trans->nmt;
|
|
|
|
trans->callref = 0;
|
|
|
|
if (!nmt) {
|
|
destroy_transaction(trans);
|
|
return;
|
|
}
|
|
|
|
switch (nmt->state) {
|
|
case STATE_ACTIVE:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Outgoing release, during active call, releasing!\n");
|
|
nmt_release(nmt);
|
|
break;
|
|
case STATE_MT_RINGING:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Outgoing release, during ringing, releasing!\n");
|
|
nmt_release(nmt);
|
|
break;
|
|
default:
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Outgoing release, when phone is in call setup, releasing!\n");
|
|
nmt_release(nmt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Receive audio from call instance. */
|
|
void call_down_audio(int callref, sample_t *samples, int count)
|
|
{
|
|
transaction_t *trans;
|
|
nmt_t *nmt;
|
|
|
|
trans = get_transaction_by_callref(callref);
|
|
if (!trans)
|
|
return;
|
|
nmt = trans->nmt;
|
|
if (!nmt)
|
|
return;
|
|
|
|
if (nmt->dsp_mode == DSP_MODE_AUDIO || nmt->dsp_mode == DSP_MODE_DTMF) {
|
|
sample_t up[(int)((double)count * nmt->sender.srstate.factor + 0.5) + 10];
|
|
if (nmt->compandor)
|
|
compress_audio(&nmt->cstate, samples, count);
|
|
count = samplerate_upsample(&nmt->sender.srstate, samples, count, up);
|
|
jitter_save(&nmt->sender.dejitter, up, count);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* SMS layer messages
|
|
*/
|
|
|
|
/* SMS layer releases */
|
|
void sms_release(nmt_t *nmt)
|
|
{
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Outgoing release, by SMS layer!\n");
|
|
nmt_release(nmt);
|
|
}
|
|
|
|
int sms_submit(nmt_t *nmt, uint8_t ref, const char *orig_address, uint8_t __attribute__((unused)) orig_type, uint8_t __attribute__((unused)) orig_plan, int __attribute__((unused)) msg_ref, const char *dest_address, uint8_t __attribute__((unused)) dest_type, uint8_t __attribute__((unused)) dest_plan, const char *message)
|
|
{
|
|
char sms[512];
|
|
|
|
if (!orig_address[0])
|
|
orig_address = &nmt->trans->subscriber.country;
|
|
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Received SMS from '%s' to '%s' (ref=%d)\n", orig_address, dest_address, ref);
|
|
printf("SMS received '%s' -> '%s': %s\n", orig_address, dest_address, message);
|
|
snprintf(sms, sizeof(sms) - 1, "%s,%s,%s", orig_address, dest_address, message);
|
|
sms[sizeof(sms) - 1] = '\0';
|
|
|
|
return submit_sms(sms);
|
|
}
|
|
|
|
void sms_deliver_report(nmt_t *nmt, uint8_t ref, int error, uint8_t cause)
|
|
{
|
|
PDEBUG_CHAN(DNMT, DEBUG_NOTICE, "Got SMS deliver report (ref=%d)\n", ref);
|
|
if (error)
|
|
printf("SMS failed! (cause=%d)\n", cause);
|
|
else {
|
|
sms_ref++;
|
|
printf("SMS sent!\n");
|
|
}
|
|
}
|
|
|
|
/* application sends ud a message, we need to deliver */
|
|
void deliver_sms(const char *sms)
|
|
{
|
|
int rc;
|
|
char buffer[strlen(sms) + 1], *p = buffer, *caller_id, *number, *message;
|
|
enum number_type caller_type;
|
|
|
|
strcpy(buffer, sms);
|
|
caller_id = strsep(&p, ",");
|
|
number = strsep(&p, ",");
|
|
message = p;
|
|
if (!caller_id || !number || !message) {
|
|
inval:
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Given SMS MUST be in the following format: [i|n|s|u]<caller ID>,<7 digits number>,<message with comma and spaces> (i, n, s, u indicate the type of number)\n");
|
|
return;
|
|
}
|
|
if (strlen(number) != 7) {
|
|
PDEBUG(DNMT, DEBUG_NOTICE, "Given number must be 7 digits\n");
|
|
goto inval;
|
|
}
|
|
|
|
switch(caller_id[0]) {
|
|
case '\0':
|
|
caller_type = TYPE_NOTAVAIL;
|
|
break;
|
|
case 'i':
|
|
caller_type = TYPE_INTERNATIONAL;
|
|
caller_id++;
|
|
break;
|
|
case 'n':
|
|
caller_type = TYPE_NATIONAL;
|
|
caller_id++;
|
|
break;
|
|
case 's':
|
|
caller_type = TYPE_SUBSCRIBER;
|
|
caller_id++;
|
|
break;
|
|
case 'u':
|
|
caller_type = TYPE_UNKNOWN;
|
|
caller_id++;
|
|
break;
|
|
default:
|
|
caller_type = TYPE_UNKNOWN;
|
|
}
|
|
|
|
PDEBUG(DNMT, DEBUG_INFO, "SMS from '%s' for subscriber '%s' with message '%s'\n", caller_id, number, message);
|
|
printf("SMS sending '%s' -> '%s': %s\n", caller_id, number, message);
|
|
|
|
rc = sms_out_setup(number, caller_id, caller_type, message);
|
|
if (rc < 0) {
|
|
PDEBUG(DNMT, DEBUG_INFO, "SMS delivery failed with cause '%d'\n", -rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void dump_info(void) {}
|
|
|