diff options
Diffstat (limited to 'src/nmt/nmt.c')
-rw-r--r-- | src/nmt/nmt.c | 1460 |
1 files changed, 1460 insertions, 0 deletions
diff --git a/src/nmt/nmt.c b/src/nmt/nmt.c new file mode 100644 index 0000000..561167e --- /dev/null +++ b/src/nmt/nmt.c @@ -0,0 +1,1460 @@ +/* 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/>. + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "../common/debug.h" +#include "../common/timer.h" +#include "../common/call.h" +#include "../common/cause.h" +#include "nmt.h" +#include "dsp.h" +#include "frame.h" + +/* 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 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_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; +} + +static void nmt_new_state(nmt_t *nmt, enum nmt_state new_state) +{ + if (nmt->state == new_state) + return; + PDEBUG(DNMT, DEBUG_DEBUG, "State change: %s -> %s\n", nmt_state_name(nmt->state), nmt_state_name(new_state)); + nmt->state = new_state; +} + +static struct nmt_channels { + enum nmt_chan_type chan_type; + const char *short_name; + const char *long_name; +} nmt_channels[] = { + { CHAN_TYPE_CC, "CC", "calling channel" }, + { CHAN_TYPE_TC, "TC", "traffic channel" }, + { CHAN_TYPE_CC_TC, "CC/TC","combined calling & traffic channel" }, + { CHAN_TYPE_TEST, "TEST", "calling channel" }, + { 0, NULL, NULL } +}; + +void nmt_channel_list(void) +{ + int i; + + printf("Type\tDescription\n"); + printf("------------------------------------------------------------------------\n"); + for (i = 0; nmt_channels[i].long_name; i++) + printf("%s\t%s\n", nmt_channels[i].short_name, nmt_channels[i].long_name); +} + +int nmt_channel_by_short_name(const char *short_name) +{ + int i; + + for (i = 0; nmt_channels[i].short_name; i++) { + if (!strcasecmp(nmt_channels[i].short_name, short_name)) { + PDEBUG(DNMT, DEBUG_INFO, "Selecting channel '%s' = %s\n", nmt_channels[i].short_name, nmt_channels[i].long_name); + return nmt_channels[i].chan_type; + } + } + + return -1; +} + +const char *chan_type_short_name(enum nmt_chan_type chan_type) +{ + int i; + + for (i = 0; nmt_channels[i].short_name; i++) { + if (nmt_channels[i].chan_type == chan_type) + return nmt_channels[i].short_name; + } + + return "invalid"; +} + +const char *chan_type_long_name(enum nmt_chan_type chan_type) +{ + int i; + + for (i = 0; nmt_channels[i].long_name; i++) { + 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 channel number to frequency number of base station. + Set 'uplink' to 1 to get frequency of mobile station. */ +double nmt_channel2freq(int channel, int uplink) +{ + double freq; + + if (channel < 1) + return 0; + else if (channel <= 180) + freq = 463.000 + (channel - 1) * 0.025; + else if (channel <= 200) + freq = 462.500 + (channel - 181) * 0.025; + else if (channel <= 380) + freq = 463.000 + (channel - 201) * 0.025 + 0.0125; + else if (channel <= 399) /* no channel 400, caused by interleaving and coding */ + freq = 462.500 + (channel - 381) * 0.025 + 0.0125; + else + return 0; + + if (uplink) + freq -= 10.000; + + return freq; +} + +/* 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; +} + +/* country selector */ +static struct nmt_country { + int y; + const char *short_name; + const char *long_name; + const char *provider_name; +} nmt_country[] = { + { 5, "DK", "Denmark", "Tele Danmark Mobile" }, + { 6, "SE", "Sweden", "Telia Mobitel" }, + { 7, "NO", "Norway", "Telenor Mobil" }, + { 8, "FI", "Finland", "Telecom Finland" }, + { 8, "IS", "Iceland", "Post & Telecom" }, + { 5, "FO", "Faroe Island", "Faroese Telecom" }, + { 7, "EE", "Estonia", "Eesti Mobiiltelefon" }, + { 5, "LV", "Latvia", "Latvian Mobile Telephone" }, + { 8, "LT", "Lithuania", "COMLIET" }, + { 6, "BY", "Belarus", "Belcel" }, + { 5, "MO", "OSS/Moscow", "Moscow Cellular Comm." }, + { 6, "STP", "OSS/St Petersburg", "Delta Telecom" }, + { 6, "STP", "OSS/Leningrads Dist.", "Delta Telecom" }, + { 7, "CAR", "OSS/Carelian Rep.", "Telecom Finland" }, + { 5, "MUR", "OSS/Murmansk", "Telecom Finland" }, + { 5, "LED", "OSS/Leningrads Dist.", "Telecom Finland" }, + { 5, "KAL", "Kaliningrad", "Telecom Finland" }, + { 7, "PL", "Poland", "CENTERTEL" }, + { 6, "BG", "Bulgaria", "MOBIFON" }, + { 5, "RO", "Romania", "Telefonica Romania" }, + { 6, "UA", "Ukraine", "Ukraine Mobile Comm." }, + { 1, "RU1", "", "" }, + { 2, "RU2", "", "" }, + { 3, "RU3", "", "" }, + { 4, "RU4", "", "" }, + { 0, NULL, NULL, NULL } +}; + +void nmt_country_list(void) +{ + int i; + + printf("TA\tShort\tCountry (Provider)\n"); + printf("------------------------------------------------------------------------\n"); + for (i = 0; nmt_country[i].short_name; i++) { + if (nmt_country[i].short_name[0]) + printf("%d\t%s\t%s (%s)\n", nmt_country[i].y, nmt_country[i].short_name, nmt_country[i].long_name, nmt_country[i].provider_name); + else + printf("%d\t%s\n", nmt_country[i].y, nmt_country[i].short_name); + } +} + +uint8_t nmt_country_by_short_name(const char *short_name) +{ + int i; + + for (i = 0; nmt_country[i].short_name; i++) { + if (!strcasecmp(nmt_country[i].short_name, short_name)) { + PDEBUG(DNMT, DEBUG_INFO, "Selecting country code %d of %s,%s (provider '%s')\n", nmt_country[i].y, nmt_country[i].short_name, nmt_country[i].long_name, nmt_country[i].provider_name); + return nmt_country[i].y; + } + } + + return 0; +} + +static void nmt_timeout(struct timer *timer); +static void nmt_go_idle(nmt_t *nmt); + +/* Create transceiver instance and link to a list. */ +int nmt_create(const char *sounddev, int samplerate, int channel, enum nmt_chan_type chan_type, uint8_t ms_power, uint8_t traffic_area, uint8_t area_no, int compander, int supervisory, int loopback) +{ + nmt_t *nmt; + int rc; + + if (channel < 1 || channel > 399) { + PDEBUG(DNMT, DEBUG_ERROR, "Channel number %d invalid.\n", channel); + return -EINVAL; + } + + if (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 (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_CC) { + PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel can be used for calling only.\n"); + PDEBUG(DNMT, DEBUG_NOTICE, "*** No call from the mobile phone is possible.\n"); + PDEBUG(DNMT, DEBUG_NOTICE, "*** Use combined 'CC/TC' instead!\n"); + } + if (chan_type == CHAN_TYPE_TC) { + PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel can be used for traffic only.\n"); + PDEBUG(DNMT, DEBUG_NOTICE, "*** No call to the mobile phone is possible.\n"); + PDEBUG(DNMT, DEBUG_NOTICE, "*** Use combined 'CC/TC' instead!\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, sounddev, samplerate, channel, loopback, 0, -1); + if (rc < 0) { + PDEBUG(DNMT, DEBUG_ERROR, "Failed to init transceiver process!\n"); + goto error; + } + + /* init audio processing */ + rc = dsp_init_sender(nmt); + if (rc < 0) { + PDEBUG(DNMT, DEBUG_ERROR, "Failed to init audio processing!\n"); + goto error; + } + + timer_init(&nmt->timer, nmt_timeout, nmt); + 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->compander = compander; + nmt->supervisory = supervisory; + + /* go into idle state */ + nmt_go_idle(nmt); + + return 0; + +error: + nmt_destroy(&nmt->sender); + + return rc; +} + +/* 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); + timer_exit(&nmt->timer); + sender_destroy(&nmt->sender); + free(nmt); +} + +/* Abort connection towards mobile station by sending idle digits. */ +static void nmt_go_idle(nmt_t *nmt) +{ + timer_stop(&nmt->timer); + + PDEBUG(DNMT, DEBUG_INFO, "Entering IDLE state, sending idle frames on %s.\n", chan_type_long_name(nmt->sysinfo.chan_type)); + nmt_new_state(nmt, STATE_IDLE); + nmt_set_dsp_mode(nmt, DSP_MODE_FRAME); + memset(&nmt->subscriber, 0, sizeof(nmt->subscriber)); + memset(&nmt->dialing, 0, sizeof(nmt->dialing)); +} + +/* release an ongoing connection, this is used by roaming update and release initiated by MTX */ +static void nmt_release(nmt_t *nmt) +{ + timer_stop(&nmt->timer); + + PDEBUG(DNMT, DEBUG_INFO, "Releasing connection towards mobile station.\n"); + 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(nmt_t *nmt, char ms_country, const char *ms_number, int try) +{ + PDEBUG(DNMT, DEBUG_INFO, "Entering paging state (try %d), sending call to '%c,%s'.\n", try, ms_country, ms_number); + nmt->subscriber.country = ms_country; + strcpy(nmt->subscriber.number, ms_number); + nmt->page_try = try; + nmt_new_state(nmt, STATE_MT_PAGING); + nmt_set_dsp_mode(nmt, DSP_MODE_FRAME); + nmt->tx_frame_count = 0; +} + +/* + * 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; + + /* check channel match */ + nmt_decode_channel(frame->channel_no, &channel, &power); + if (channel != nmt->sender.kanal) { + PDEBUG(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(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(DNMT, DEBUG_NOTICE, "Received traffic 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(nmt_t *nmt, frame_t *frame) +{ + if (nmt_digits2value(&nmt->subscriber.country, 1) != frame->ms_country) { + PDEBUG(DNMT, DEBUG_NOTICE, "Received non matching subscriber counrtry, ignoring.\n"); + return 0; + } + if (nmt_digits2value(nmt->subscriber.number, 6) != frame->ms_number) { + PDEBUG(DNMT, DEBUG_NOTICE, "Received non matching subscriber number, ignoring.\n"); + return 0; + } + + return 1; +} + +/* + * handle idle channel + */ + +static void tx_idle(nmt_t *nmt, frame_t *frame) +{ + switch (nmt->sysinfo.chan_type) { + case CHAN_TYPE_CC: + frame->index = NMT_MESSAGE_1a; + break; + case CHAN_TYPE_TC: + frame->index = NMT_MESSAGE_4; + break; + case CHAN_TYPE_CC_TC: + frame->index = NMT_MESSAGE_1b; + break; + case CHAN_TYPE_TEST: + frame->index = NMT_MESSAGE_30; + break; + } + frame->channel_no = nmt_encode_channel(nmt->sender.kanal, nmt->sysinfo.ms_power); + frame->traffic_area = 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) +{ + switch (frame->index) { + case NMT_MESSAGE_11a: /* roaming update and seizure */ + if (!match_channel(nmt, frame)) + break; + if (!match_area(nmt, frame)) + break; + /* set subscriber */ + nmt_value2digits(frame->ms_country, &nmt->subscriber.country, 1); + nmt_value2digits(frame->ms_number, nmt->subscriber.number, 6); + nmt->subscriber.number[6] = '\0'; + + PDEBUG(DNMT, DEBUG_INFO, "Received roaming seizure from subscriber %c,%s\n", nmt->subscriber.country, nmt->subscriber.number); + /* change state */ + nmt_new_state(nmt, STATE_ROAMING_IDENT); + 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 */ + if (!match_channel(nmt, frame)) + break; + if (!match_area(nmt, frame)) + break; + /* set subscriber */ + nmt_value2digits(frame->ms_country, &nmt->subscriber.country, 1); + nmt_value2digits(frame->ms_number, nmt->subscriber.number, 6); + if (frame->index == NMT_MESSAGE_12) + nmt->subscriber.coinbox = 1; + nmt->subscriber.number[6] = '\0'; + + PDEBUG(DNMT, DEBUG_INFO, "Received call from subscriber %c,%s%s\n", nmt->subscriber.country, nmt->subscriber.number, (nmt->subscriber.coinbox) ? " (coinbox)" : ""); + /* change state */ + nmt_new_state(nmt, STATE_MO_IDENT); + nmt->rx_frame_count = 0; + nmt->tx_frame_count = 0; + break; + /* signals after release */ + case NMT_MESSAGE_13a: /* line signal */ + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), nmt_state_name(nmt->state)); + } +} + +/* + * handle roaming + */ + +static void tx_ident(nmt_t *nmt, frame_t *frame) +{ + frame->index = NMT_MESSAGE_3b; + frame->channel_no = nmt_encode_channel(nmt->sender.kanal, nmt->sysinfo.ms_power); + frame->traffic_area = nmt->sysinfo.traffic_area; + frame->ms_country = nmt_digits2value(&nmt->subscriber.country, 1); + frame->ms_number = nmt_digits2value(nmt->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) +{ + frame->index = NMT_MESSAGE_5a; + frame->channel_no = nmt_encode_channel(nmt->sender.kanal, nmt->sysinfo.ms_power); + frame->traffic_area = nmt->sysinfo.traffic_area; + frame->ms_country = nmt_digits2value(&nmt->subscriber.country, 1); + frame->ms_number = nmt_digits2value(nmt->subscriber.number, 6); + frame->line_signal = (signal << 8) | (signal << 4) | signal; +} + +static void tx_roaming_ident(nmt_t *nmt, frame_t *frame) +{ + if (++nmt->tx_frame_count == 1) + PDEBUG(DNMT, DEBUG_INFO, "Sending identity request.\n"); + if (nmt->tx_frame_count == 8) { + nmt_release(nmt); + } + tx_ident(nmt, frame); +} + +static void rx_roaming_ident(nmt_t *nmt, frame_t *frame) +{ + switch (frame->index) { + case NMT_MESSAGE_11a: /* roaming update */ + if (!match_channel(nmt, frame)) + break; + if (!match_area(nmt, frame)) + break; + if (!match_subscriber(nmt, frame)) + break; + if (nmt->rx_frame_count < 2) { + PDEBUG(DNMT, DEBUG_DEBUG, "Skipping second seizure frame\n"); + break; + } + nmt_value2digits(frame->ms_password, nmt->subscriber.password, 3); + PDEBUG(DNMT, DEBUG_INFO, "Received identity confirm (password %s).\n", nmt->subscriber.password); + nmt_new_state(nmt, STATE_ROAMING_CONFIRM); + nmt->tx_frame_count = 0; + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), 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(DNMT, DEBUG_INFO, "Send 'ready to receive'.\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->index) { + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), 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(DNMT, DEBUG_INFO, "Sending identity request.\n"); + if (nmt->tx_frame_count == 8) { + nmt_release(nmt); + } + tx_ident(nmt, frame); +} + +static void rx_mo_ident(nmt_t *nmt, frame_t *frame) +{ + switch (frame->index) { + case NMT_MESSAGE_10b: /* seizure */ + case NMT_MESSAGE_12: /* seizure */ + if (!match_channel(nmt, frame)) + break; + if (!match_subscriber(nmt, frame)) + break; + if (nmt->rx_frame_count < 2) { + PDEBUG(DNMT, DEBUG_DEBUG, "Skipping second seizure frame\n"); + break; + } + nmt_value2digits(frame->ms_password, nmt->subscriber.password, 3); + PDEBUG(DNMT, DEBUG_INFO, "Received identity confirm (password %s).\n", nmt->subscriber.password); + nmt_new_state(nmt, STATE_MO_CONFIRM); + nmt->tx_frame_count = 0; + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), 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(DNMT, DEBUG_INFO, "Send 'ready to receive'.\n"); + } else { + if (nmt->tx_frame_count == 3) { + PDEBUG(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); + + switch (frame->index) { + case NMT_MESSAGE_14a: /* digits */ + if (!match_channel(nmt, frame)) + break; + if (!match_area(nmt, frame)) + break; + timer_start(&nmt->timer, DIALING_TO); + /* max digits received */ + if (len + 1 == sizeof(nmt->dialing)) + break; + /* received odd digit, but be already have odd number of digits */ + if ((len & 1)) { + if (nmt->rx_frame_count > 1) + goto missing_digit; + break; + } else if (len) { + if (nmt->rx_frame_count > 3) + goto missing_digit; + } + nmt->dialing[len] = nmt_value2digit(frame->digit); + nmt->dialing[len + 1] = '\0'; + PDEBUG(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; + timer_start(&nmt->timer, DIALING_TO); + /* max digits received */ + if (len + 1 == sizeof(nmt->dialing)) + break; + /* received odd digit, but be already have odd number of digits */ + if (!(len & 1)) { + if (len && nmt->rx_frame_count > 1) + goto missing_digit; + break; + } else { + if (nmt->rx_frame_count > 3) + goto missing_digit; + } + nmt->dialing[len] = nmt_value2digit(frame->digit); + nmt->dialing[len + 1] = '\0'; + PDEBUG(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(DNMT, DEBUG_INFO, "Dialing complete %s->%s, call established.\n", &nmt->subscriber.country, nmt->dialing); + /* setup call */ + { + int callref = ++new_callref; + int rc; + PDEBUG(DNMT, DEBUG_INFO, "Setup call to network.\n"); + rc = call_in_setup(callref, &nmt->subscriber.country, nmt->dialing); + if (rc < 0) { + PDEBUG(DNMT, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", rc); + nmt_release(nmt); + return; + } + nmt->sender.callref = callref; + } + nmt_new_state(nmt, STATE_MO_COMPLETE); + nmt_set_dsp_mode(nmt, DSP_MODE_FRAME); + nmt->tx_frame_count = 0; + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), nmt_state_name(nmt->state)); + } + + return; + +missing_digit: + PDEBUG(DNMT, DEBUG_NOTICE, "Missing digit, aborting.\n"); + nmt_release(nmt); +} + +static void tx_mo_complete(nmt_t *nmt, frame_t *frame) +{ + if (++nmt->tx_frame_count <= 4) { + set_line_signal(nmt, frame, 6); + if (nmt->tx_frame_count == 1) + PDEBUG(DNMT, DEBUG_INFO, "Send 'addess complete'.\n"); + } else { + if (nmt->compander) { + set_line_signal(nmt, frame, 5); + if (nmt->tx_frame_count == 5) + PDEBUG(DNMT, DEBUG_INFO, "Send 'compander in'.\n"); + } else + frame->index = NMT_MESSAGE_6; + if (nmt->tx_frame_count == 9) { + PDEBUG(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) { + super_reset(nmt); + timer_start(&nmt->timer, SUPERVISORY_TO1); + } + } + } +} + +static void timeout_mo_dialing(nmt_t *nmt) +{ + PDEBUG(DNMT, DEBUG_NOTICE, "Timeout while receiving digits.\n"); + nmt_release(nmt); + PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n"); + call_in_release(nmt->sender.callref, CAUSE_TEMPFAIL); + nmt->sender.callref = 0; +} + +/* + * handle call MTX -> MS + */ +static void tx_mt_paging(nmt_t *nmt, frame_t *frame) +{ + frame->index = NMT_MESSAGE_2a; + frame->channel_no = nmt_encode_channel(nmt->sender.kanal, nmt->sysinfo.ms_power); + frame->traffic_area = nmt->sysinfo.traffic_area; + frame->ms_country = nmt_digits2value(&nmt->subscriber.country, 1); + frame->ms_number = nmt_digits2value(nmt->subscriber.number, 6); + frame->additional_info = nmt_encode_area_no(nmt->sysinfo.area_no); + if (++nmt->tx_frame_count == 1) { + PDEBUG(DNMT, DEBUG_INFO, "Send call to mobile.\n"); + } else + tx_idle(nmt, frame); + /* wait some time to get answer. use more than one frame due to delay of audio processing */ + if (nmt->tx_frame_count == 5) { + PDEBUG(DNMT, DEBUG_NOTICE, "No answer from mobile phone (try %d).\n", nmt->page_try); + if (nmt->page_try == PAGE_TRIES) { + PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n"); + call_in_release(nmt->sender.callref, CAUSE_OUTOFORDER); + nmt->sender.callref = 0; + nmt_go_idle(nmt); + return; + } + nmt_page(nmt, nmt->subscriber.country, nmt->subscriber.number, nmt->page_try + 1); + } +} + +static void rx_mt_paging(nmt_t *nmt, frame_t *frame) +{ + switch (frame->index) { + case NMT_MESSAGE_10a: /* call acknowledgement */ + if (!match_channel(nmt, frame)) + break; + if (!match_subscriber(nmt, frame)) + break; + PDEBUG(DNMT, DEBUG_INFO, "Received call acknowledgement.\n"); + nmt_new_state(nmt, STATE_MT_CHANNEL); + nmt->tx_frame_count = 0; + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), nmt_state_name(nmt->state)); + } +} + +static void tx_mt_channel(nmt_t *nmt, frame_t *frame) +{ + frame->index = NMT_MESSAGE_2b; + frame->channel_no = nmt_encode_channel(nmt->sender.kanal, nmt->sysinfo.ms_power); + frame->traffic_area = nmt->sysinfo.traffic_area; + frame->ms_country = nmt_digits2value(&nmt->subscriber.country, 1); + frame->ms_number = nmt_digits2value(nmt->subscriber.number, 6); + frame->tc_no = nmt_encode_channel(nmt->sender.kanal, nmt->sysinfo.ms_power); + PDEBUG(DNMT, DEBUG_INFO, "Send channel activation to mobile.\n"); + nmt_new_state(nmt, STATE_MT_IDENT); +} + +static void tx_mt_ident(nmt_t *nmt, frame_t *frame) +{ + if (++nmt->tx_frame_count == 1) + PDEBUG(DNMT, DEBUG_INFO, "Sending identity request.\n"); + if (nmt->tx_frame_count == 8) { + PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n"); + call_in_release(nmt->sender.callref, CAUSE_TEMPFAIL); + nmt->sender.callref = 0; + nmt_go_idle(nmt); + } + tx_ident(nmt, frame); +} + +static void rx_mt_ident(nmt_t *nmt, frame_t *frame) +{ + switch (frame->index) { + case NMT_MESSAGE_10b: /* seizure */ + if (!match_subscriber(nmt, frame)) + break; + nmt_value2digits(frame->ms_password, nmt->subscriber.password, 3); + PDEBUG(DNMT, DEBUG_INFO, "Received identity (password %s).\n", nmt->subscriber.password); + nmt_new_state(nmt, STATE_MT_RINGING); + nmt->tx_frame_count = 0; + timer_start(&nmt->timer, RINGING_TO); + call_in_alerting(nmt->sender.callref); + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), nmt_state_name(nmt->state)); + } +} + +static void tx_mt_ringing(nmt_t *nmt, frame_t *frame) +{ + set_line_signal(nmt, frame, 9); + if (++nmt->tx_frame_count == 1) + PDEBUG(DNMT, DEBUG_INFO, "Send 'ringing order'.\n"); + if (nmt->tx_frame_count >= 4) + frame->index = NMT_MESSAGE_6; + /* 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) +{ + switch (frame->index) { + case NMT_MESSAGE_13a: /* line signal */ + if (!match_subscriber(nmt, frame)) + break; + if ((frame->line_signal & 0xf) != 14) + break; + PDEBUG(DNMT, DEBUG_INFO, "Received 'answer' from phone.\n"); + nmt_new_state(nmt, STATE_MT_COMPLETE); + nmt->tx_frame_count = 0; + call_in_answer(nmt->sender.callref, &nmt->subscriber.country); + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), nmt_state_name(nmt->state)); + } +} + +static void tx_mt_complete(nmt_t *nmt, frame_t *frame) +{ + set_line_signal(nmt, frame, 5); + ++nmt->tx_frame_count; + if (nmt->compander) { + if (nmt->tx_frame_count == 1) + PDEBUG(DNMT, DEBUG_INFO, "Send 'compander in'.\n"); + } else + frame->index = NMT_MESSAGE_6; + if (nmt->tx_frame_count == 5) { + PDEBUG(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) { + super_reset(nmt); + timer_start(&nmt->timer, SUPERVISORY_TO1); + } + } +} + +static void timeout_mt_ringing(nmt_t *nmt) +{ + PDEBUG(DNMT, DEBUG_NOTICE, "Timeout while waiting for answer of the phone.\n"); + nmt_release(nmt); + PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n"); + call_in_release(nmt->sender.callref, CAUSE_NOANSWER); + nmt->sender.callref = 0; +} + +/* + * handle clearing towards MTX + */ + +static void tx_mo_release(nmt_t *nmt, frame_t *frame) +{ + set_line_signal(nmt, frame, 15); + if (++nmt->tx_frame_count == 1) + PDEBUG(DNMT, DEBUG_INFO, "Send release.\n"); + if (nmt->tx_frame_count == 4) + nmt_go_idle(nmt); /* 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(DNMT, DEBUG_INFO, "Send release.\n"); +} + +static void rx_mt_release(nmt_t *nmt, frame_t *frame) +{ + switch (frame->index) { + case NMT_MESSAGE_13a: /* line signal */ + if (!match_channel(nmt, frame)) + break; + if (!match_subscriber(nmt, frame)) + break; + if ((frame->line_signal & 0xf) != 1) + break; + PDEBUG(DNMT, DEBUG_INFO, "Received release guard.\n"); + nmt_go_idle(nmt); + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), nmt_state_name(nmt->state)); + } +} + +static void timeout_mt_release(nmt_t *nmt) +{ + PDEBUG(DNMT, DEBUG_NOTICE, "Timeout while releasing.\n"); + nmt_go_idle(nmt); +} + +/* + * handle call + */ + +void nmt_rx_super(nmt_t *nmt, int tone, double quality) +{ + if (tone) + PDEBUG(DNMT, DEBUG_INFO, "Detected supervisory signal with quality=%.0f.\n", quality * 100.0); + else + PDEBUG(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) +{ + if (duration == SUPERVISORY_TO1) + PDEBUG(DNMT, DEBUG_NOTICE, "Timeout after %.0f seconds not receiving supervisory signal.\n", duration); + else + PDEBUG(DNMT, DEBUG_NOTICE, "Timeout after %.0f seconds loosing supervisory signal.\n", duration); + nmt_release(nmt); + PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n"); + call_in_release(nmt->sender.callref, CAUSE_TEMPFAIL); + nmt->sender.callref = 0; +} + +static void rx_active(nmt_t *nmt, frame_t *frame) +{ + char digit; + + /* restart timer on every reception of frame */ + if (nmt->supervisory) + timer_start(&nmt->timer, SUPERVISORY_TO2); + + switch (frame->index) { + case NMT_MESSAGE_13a: /* line signal */ + if (!match_channel(nmt, frame)) + break; + if (!match_subscriber(nmt, frame)) + break; + switch ((frame->line_signal & 0xf)) { + case 5: + PDEBUG(DNMT, DEBUG_NOTICE, "Register Recall is not supported.\n"); + break; + case 8: + if (nmt->active_state != ACTIVE_STATE_VOICE) + break; + PDEBUG(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(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; + digit = nmt_value2digit(frame->digit); + dtmf_set_tone(&nmt->dtmf, digit); + PDEBUG(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; + digit = nmt_value2digit(frame->digit); + dtmf_set_tone(&nmt->dtmf, digit); + PDEBUG(DNMT, DEBUG_INFO, "Received (even) digit %c.\n", digit); + nmt->mft_num++; + break; + default: + PDEBUG(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame->index), 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(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(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, double frames_elapsed) +{ + frame_t frame; + int rc; + + rc = decode_frame(&frame, bits, (nmt->sender.loopback) ? MTX_TO_XX : XX_TO_MTX, (nmt->state == STATE_MT_PAGING)); + if (rc < 0) { + PDEBUG(DNMT, (nmt->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received invalid frame. (quality=%.0f%% level=%.0f%%)\n", quality * 100.0, level * 100.0); + return; + } + + /* frame counter */ + nmt->rx_frame_count += (int)(frames_elapsed + 0.5); + + PDEBUG(DNMT, (nmt->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received frame %s (quality=%.0f%% level=%.0f%%)\n", nmt_frame_name(frame.index), quality * 100.0, level * 100.0); + + if (nmt->sender.loopback) + return; + + /* MS releases, but this is not the acknowledge of MTX release */ + if (frame.index == 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, &frame)) + return; + PDEBUG(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->sender.callref) { + PDEBUG(DNMT, DEBUG_INFO, "Release call towards network.\n"); + call_in_release(nmt->sender.callref, CAUSE_NORMAL); + nmt->sender.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_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(DNMT, DEBUG_DEBUG, "Dropping message %s in state %s\n", nmt_frame_name(frame.index), 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_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.index) { + case NMT_MESSAGE_1a: + case NMT_MESSAGE_4: + case NMT_MESSAGE_1b: + case NMT_MESSAGE_30: + debug = 0; + break; + } + + /* frame sending aborted (e.g. due to audio) */ + if (nmt->dsp_mode != DSP_MODE_FRAME) + return NULL; + + bits = encode_frame(&frame, debug); + + PDEBUG(DNMT, DEBUG_DEBUG, "Sending frame %s.\n", nmt_frame_name(frame.index)); + return bits; +} + +/* + * call states received from call control + */ + +/* Call control starts call towards mobile station. */ +int call_out_setup(int callref, char *dialing) +{ + sender_t *sender; + nmt_t *nmt; + int i; + char ms_country; + char ms_number[7] = "000000"; + + /* 1. check if number is invalid, return INVALNUMBER */ + if (dialstring2number(dialing, &ms_country, ms_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 (ms_number[i] < '0' || ms_number[i] > '9') + goto inval; + } + + /* 2. check if given number is already in a call, return BUSY */ + for (sender = sender_head; sender; sender = sender->next) { + nmt = (nmt_t *) sender; + if (nmt->subscriber.country == ms_country && !strcmp(nmt->subscriber.number, ms_number)) + break; + } + if (sender) { + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n"); + return -CAUSE_BUSY; + } + + /* 3. check if all senders are busy, return NOCHANNEL */ + for (sender = sender_head; sender; sender = sender->next) { + nmt = (nmt_t *) sender; + if (nmt->sysinfo.chan_type != CHAN_TYPE_CC + && nmt->sysinfo.chan_type != CHAN_TYPE_CC_TC) + 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; + } + + PDEBUG(DNMT, DEBUG_INFO, "Call to mobile station, paging station id '%c%s'\n", ms_country, ms_number); + + /* 4. trying to page mobile station */ + sender->callref = callref; + nmt_page(nmt, ms_country, ms_number, 1); + + return 0; +} + +/* Call control sends disconnect (with tones). + * An active call stays active, so tones and annoucements can be received + * by mobile station. + */ +void call_out_disconnect(int callref, int cause) +{ + sender_t *sender; + nmt_t *nmt; + + PDEBUG(DNMT, DEBUG_INFO, "Call has been disconnected by network.\n"); + + for (sender = sender_head; sender; sender = sender->next) { + nmt = (nmt_t *) sender; + if (sender->callref == callref) + break; + } + if (!sender) { + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); + call_in_release(callref, CAUSE_INVALCALLREF); + return; + } + + /* Release when not active */ + if (nmt->state == STATE_ACTIVE) + return; + switch (nmt->state) { + case STATE_MT_RINGING: + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing disconnect, during ringing, releasing!\n"); + nmt_release(nmt); + break; + default: + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing disconnect, when phone is in call setup, releasing!\n"); + nmt_release(nmt); + break; + } + + call_in_release(callref, cause); + + sender->callref = 0; +} + +/* Call control releases call toward mobile station. */ +void call_out_release(int callref, int cause) +{ + sender_t *sender; + nmt_t *nmt; + + PDEBUG(DNMT, DEBUG_INFO, "Call has been released by network, releasing call.\n"); + + for (sender = sender_head; sender; sender = sender->next) { + nmt = (nmt_t *) sender; + if (sender->callref == callref) + break; + } + if (!sender) { + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing release, but no callref!\n"); + /* don't send release, because caller already released */ + return; + } + + sender->callref = 0; + + switch (nmt->state) { + case STATE_ACTIVE: + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing release, during active call, releasing!\n"); + nmt_release(nmt); + break; + case STATE_MT_RINGING: + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing release, during ringing, releasing!\n"); + nmt_release(nmt); + break; + default: + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing release, when phone is in call setup, releasing!\n"); + nmt_release(nmt); + break; + } +} + +/* Receive audio from call instance. */ +void call_rx_audio(int callref, int16_t *samples, int count) +{ + sender_t *sender; + nmt_t *nmt; + + for (sender = sender_head; sender; sender = sender->next) { + nmt = (nmt_t *) sender; + if (sender->callref == callref) + break; + } + if (!sender) + return; + + if (nmt->dsp_mode == DSP_MODE_AUDIO || nmt->dsp_mode == DSP_MODE_DTMF) { + int16_t up[count * nmt->sender.srstate.factor]; + if (nmt->compander) + compress_audio(&nmt->cstate, samples, count); + count = samplerate_upsample(&nmt->sender.srstate, samples, count, up); + jitter_save(&nmt->sender.audio, up, count); + } +} + |