diff options
author | Andreas Eversberg <jolly@eversberg.eu> | 2016-07-04 19:52:00 +0200 |
---|---|---|
committer | Andreas Eversberg <jolly@eversberg.eu> | 2016-07-09 15:16:16 +0200 |
commit | e7fa08b6dfdfb607264179b457276d18c24da269 (patch) | |
tree | 1f51334dfeccb6cda2dd0671b53d648418e88b8a /src/nmt | |
parent | 64c829909bc5b73a7765272ad07cf6465762aeac (diff) |
NMT / SMS: Short Message Service support
Diffstat (limited to 'src/nmt')
-rw-r--r-- | src/nmt/Makefile.am | 1 | ||||
-rw-r--r-- | src/nmt/main.c | 68 | ||||
-rw-r--r-- | src/nmt/nmt.c | 98 | ||||
-rw-r--r-- | src/nmt/nmt.h | 10 | ||||
-rw-r--r-- | src/nmt/sms.c | 681 | ||||
-rw-r--r-- | src/nmt/sms.h | 34 |
6 files changed, 883 insertions, 9 deletions
diff --git a/src/nmt/Makefile.am b/src/nmt/Makefile.am index dee1ccd..a93e45b 100644 --- a/src/nmt/Makefile.am +++ b/src/nmt/Makefile.am @@ -8,6 +8,7 @@ nmt_SOURCES = \ dsp.c \ frame.c \ dms.c \ + sms.c \ image.c \ tones.c \ announcement.c \ diff --git a/src/nmt/main.c b/src/nmt/main.c index 3e9356b..352545d 100644 --- a/src/nmt/main.c +++ b/src/nmt/main.c @@ -24,6 +24,10 @@ #include <string.h> #include <signal.h> #include <sched.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> #include "../common/main.h" #include "../common/debug.h" #include "../common/timer.h" @@ -36,6 +40,9 @@ #include "tones.h" #include "announcement.h" +#define SMS_FIFO "/tmp/nmt_sms_deliver" +static int sms_fd = -1; + /* settings */ int num_chan_type = 0; enum nmt_chan_type chan_type[MAX_SENDER] = { CHAN_TYPE_CC_TC }; @@ -44,6 +51,7 @@ char traffic_area[3] = ""; char area_no = 0; int compandor = 1; int supervisory = 0; +const char *smsc_number = "767"; void print_help(const char *arg0) { @@ -69,6 +77,9 @@ void print_help(const char *arg0) printf(" -0 --supervisory 1..4 | 0\n"); printf(" Use supervisory signal 1..4 to detect loss of signal from mobile\n"); printf(" station, use 0 to disable. (default = '%d')\n", supervisory); + printf(" -S --smsc-number <digits>\n"); + printf(" If this number is dialed, the mobile is connected to the SMSC (Short\n"); + printf(" Message Service Center). (default = '%s')\n", smsc_number); printf("\nstation-id: Give 7 digits of station-id, you don't need to enter it\n"); printf(" for every start of this program.\n"); } @@ -85,10 +96,11 @@ static int handle_options(int argc, char **argv) {"traffic-area", 1, 0, 'y'}, {"compandor", 1, 0, 'C'}, {"supervisory", 1, 0, '0'}, + {"smsc-number", 1, 0, 'S'}, {0, 0, 0, 0} }; - set_options_common("t:P:a:y:C:0:", long_options_special); + set_options_common("t:P:a:y:C:0:S:", long_options_special); while (1) { int option_index = 0, c, rc; @@ -176,6 +188,10 @@ error_ta: } skip_args += 2; break; + case 'S': + smsc_number = strdup(optarg); + skip_args += 2; + break; default: opt_switch_common(c, argv[0], &skip_args); } @@ -186,6 +202,33 @@ error_ta: return skip_args; } +static void myhandler(void) +{ + static char buffer[256]; + static int pos = 0, rc, i; + int space = sizeof(buffer) - pos; + + rc = read(sms_fd, buffer + pos, space); + if (rc > 0) { + pos += rc; + if (pos == space) { + fprintf(stderr, "SMS buffer overflow!\n"); + pos = 0; + } + /* check for end of line */ + for (i = 0; i < pos; i++) { + if (buffer[i] == '\r' || buffer[i] == '\n') + break; + } + /* send sms */ + if (i < pos) { + buffer[i] = '\0'; + pos = 0; + deliver_sms(buffer); + } + } +} + int main(int argc, char *argv[]) { int rc; @@ -237,6 +280,20 @@ int main(int argc, char *argv[]) return 0; } + /* create pipe for SMS delivery */ + unlink(SMS_FIFO); + rc = mkfifo(SMS_FIFO, 0666); + if (rc < 0) { + fprintf(stderr, "Failed to create SMS deliver FIFO!\n"); + goto fail; + } else { + sms_fd = open(SMS_FIFO, O_RDONLY | O_NONBLOCK); + if (sms_fd < 0) { + fprintf(stderr, "Failed to open SMS deliver FIFO!\n"); + goto fail; + } + } + if (!loopback) print_image(); @@ -258,7 +315,7 @@ int main(int argc, char *argv[]) /* create transceiver instance */ for (i = 0; i < num_kanal; i++) { - rc = nmt_create(kanal[i], (loopback) ? CHAN_TYPE_TEST : chan_type[i], sounddev[i], samplerate, cross_channels, rx_gain, do_pre_emphasis, do_de_emphasis, write_wave, read_wave, ms_power, nmt_digits2value(traffic_area, 2), area_no, compandor, supervisory, loopback); + rc = nmt_create(kanal[i], (loopback) ? CHAN_TYPE_TEST : chan_type[i], sounddev[i], samplerate, cross_channels, rx_gain, do_pre_emphasis, do_de_emphasis, write_wave, read_wave, ms_power, nmt_digits2value(traffic_area, 2), area_no, compandor, supervisory, smsc_number, loopback); if (rc < 0) { fprintf(stderr, "Failed to create transceiver instance. Quitting!\n"); goto fail; @@ -286,7 +343,7 @@ int main(int argc, char *argv[]) fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio); } - main_loop(&quit, latency, interval, NULL); + main_loop(&quit, latency, interval, myhandler); if (rt_prio > 0) { struct sched_param schedp; @@ -297,6 +354,11 @@ int main(int argc, char *argv[]) } fail: + /* fifo */ + if (sms_fd > 0) + close(sms_fd); + unlink(SMS_FIFO); + /* cleanup functions */ call_cleanup(); if (use_mncc_sock) diff --git a/src/nmt/nmt.c b/src/nmt/nmt.c index dbd5ff8..5c07e81 100644 --- a/src/nmt/nmt.c +++ b/src/nmt/nmt.c @@ -22,6 +22,7 @@ #include <stdlib.h> #include <string.h> #include <errno.h> +#include <time.h> #include "../common/debug.h" #include "../common/timer.h" #include "../common/call.h" @@ -30,6 +31,8 @@ #include "dsp.h" #include "frame.h" +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; @@ -283,7 +286,7 @@ 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(int channel, enum nmt_chan_type chan_type, const char *sounddev, int samplerate, int cross_channels, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_wave, const char *read_wave, uint8_t ms_power, uint8_t traffic_area, uint8_t area_no, int compandor, int supervisory, int loopback) +int nmt_create(int channel, enum nmt_chan_type chan_type, const char *sounddev, int samplerate, int cross_channels, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_wave, const char *read_wave, uint8_t ms_power, uint8_t traffic_area, uint8_t area_no, int compandor, int supervisory, const char *smsc_number, int loopback) { nmt_t *nmt; int rc; @@ -337,13 +340,20 @@ int nmt_create(int channel, enum nmt_chan_type chan_type, const char *sounddev, goto error; } - /* init audio processing */ + /* 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; + } + timer_init(&nmt->timer, nmt_timeout, nmt); nmt->sysinfo.chan_type = chan_type; nmt->sysinfo.ms_power = ms_power; @@ -351,6 +361,7 @@ int nmt_create(int channel, enum nmt_chan_type chan_type, const char *sounddev, nmt->sysinfo.area_no = area_no; nmt->compandor = compandor; nmt->supervisory = supervisory; + strncpy(nmt->smsc_number, smsc_number, sizeof(nmt->smsc_number) - 1); /* go into idle state */ nmt_go_idle(nmt); @@ -371,6 +382,7 @@ void nmt_destroy(sender_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); @@ -382,6 +394,8 @@ static void nmt_go_idle(nmt_t *nmt) timer_stop(&nmt->timer); nmt->page_for_nmt = NULL; nmt->dms_call = 0; + dms_reset(nmt); + sms_reset(nmt); 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); @@ -760,9 +774,9 @@ static void rx_mo_dialing(nmt_t *nmt, frame_t *frame) break; PDEBUG(DNMT, DEBUG_INFO, "Dialing complete %s->%s, call established.\n", &nmt->subscriber.country, nmt->dialing); /* setup call */ - if (!strcmp(nmt->dialing, "767")) { + if (!strcmp(nmt->dialing, nmt->smsc_number)) { /* SMS */ - dms_reset(nmt); + PDEBUG(DNMT, DEBUG_INFO, "Setup call to SMSC.\n"); nmt->dms_call = 1; } else { int callref = ++new_callref; @@ -980,6 +994,10 @@ static void tx_mt_complete(nmt_t *nmt, frame_t *frame) super_reset(nmt); timer_start(&nmt->timer, SUPERVISORY_TO1); } + if (nmt->dms_call) { + time_t ti = time(NULL); + sms_deliver(nmt, sms_ref, "", SMS_TYPE_UKNOWN, SMS_PLAN_ISDN_TEL, ti, nmt->sms_string); + } } } @@ -1365,7 +1383,7 @@ const char *nmt_get_frame(nmt_t *nmt) */ /* Call control starts call towards mobile station. */ -int call_out_setup(int callref, char *dialing) +int _out_setup(int callref, char *dialing, const char *sms) { sender_t *sender; nmt_t *nmt; @@ -1413,10 +1431,22 @@ inval: /* 4. trying to page mobile station */ sender->callref = callref; + if (sms) { + strncpy(nmt->sms_string, sms, sizeof(nmt->sms_string) - 1); + nmt->dms_call = 1; + } nmt_page(nmt, ms_country, ms_number, 1); return 0; } +int call_out_setup(int callref, char *dialing) +{ + return _out_setup(callref, dialing, NULL); +} +int sms_out_setup(char *dialing, const char *sms) +{ + return _out_setup(0, dialing, sms); +} /* Call control sends disconnect (with tones). * An active call stays active, so tones and annoucements can be received @@ -1519,3 +1549,61 @@ void call_rx_audio(int callref, int16_t *samples, int count) } } +/* + * SMS layer messages + */ + +/* SMS layer releases */ +void sms_release(nmt_t *nmt) +{ + PDEBUG(DNMT, DEBUG_NOTICE, "Outgoing release, by SMS layer!\n"); + nmt_release(nmt); +} + +void sms_submit(nmt_t *nmt, uint8_t ref, const char *orig_address, uint8_t orig_type, uint8_t orig_plan, int msg_ref, const char *dest_address, uint8_t dest_type, uint8_t dest_plan, const char *message) +{ + PDEBUG(DNMT, DEBUG_NOTICE, "Received SMS from '%s' to '%s'\n", orig_address, dest_address); + printf("SMS received '%s' -> '%s': %s\n", orig_address, dest_address, message); + +} + +void sms_deliver_report(nmt_t *nmt, uint8_t ref, int error, uint8_t cause) +{ + PDEBUG(DNMT, DEBUG_NOTICE, "Got SMS deliver report\n"); + 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 i, rc; + char number[8]; + + /* check for number digits */ + for (i = 0; i < 7; i++) { + if (sms[i] < '0' || sms[i] > '9') + break; + } + if (i < 7 || sms[7] != ',') { + PDEBUG(DNMT, DEBUG_NOTICE, "Given SMS MUST start with the 7 digits phone number, followed by a comma (no spaces)\n"); + return; + } + strncpy(number, sms, 7); + number[7] = '\0'; + sms += 8; + PDEBUG(DNMT, DEBUG_INFO, "SMS for subscriber '%s'\n", number); + printf("SMS sending SMSC -> '%s': %s\n", number, sms); + + rc = sms_out_setup(number, sms); + if (rc < 0) { + PDEBUG(DNMT, DEBUG_INFO, "SMS delivery failed with cause '%d'\n", -rc); + return; + } +} + + diff --git a/src/nmt/nmt.h b/src/nmt/nmt.h index 7a8c1d7..2ef1ba8 100644 --- a/src/nmt/nmt.h +++ b/src/nmt/nmt.h @@ -2,6 +2,7 @@ #include "../common/compandor.h" #include "../common/dtmf.h" #include "dms.h" +#include "sms.h" enum dsp_mode { DSP_MODE_DIALTONE, /* stream dial tone to mobile phone */ @@ -134,6 +135,12 @@ typedef struct nmt { /* DMS states */ int dms_call; /* indicates that this call is a DMS call */ dms_t dms; /* DMS states */ + + /* SMS states */ + char smsc_number[33]; /* digits to match SMSC */ + sms_t sms; /* SMS states */ + struct timer sms_timer; + char sms_string[256]; /* current string to deliver */ } nmt_t; void nmt_channel_list(void); @@ -143,9 +150,10 @@ const char *chan_type_long_name(enum nmt_chan_type chan_type); double nmt_channel2freq(int channel, int uplink); void nmt_country_list(void); uint8_t nmt_country_by_short_name(const char *short_name); -int nmt_create(int channel, enum nmt_chan_type chan_type, const char *sounddev, int samplerate, int cross_channels, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_wave, const char *read_wave, uint8_t ms_power, uint8_t traffic_area, uint8_t area_no, int compandor, int supervisory, int loopback); +int nmt_create(int channel, enum nmt_chan_type chan_type, const char *sounddev, int samplerate, int cross_channels, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_wave, const char *read_wave, uint8_t ms_power, uint8_t traffic_area, uint8_t area_no, int compandor, int supervisory, const char *smsc_number, int loopback); void nmt_destroy(sender_t *sender); void nmt_receive_frame(nmt_t *nmt, const char *bits, double quality, double level, double frames_elapsed); const char *nmt_get_frame(nmt_t *nmt); void nmt_rx_super(nmt_t *nmt, int tone, double quality); +void deliver_sms(const char *sms); diff --git a/src/nmt/sms.c b/src/nmt/sms.c new file mode 100644 index 0000000..484b68e --- /dev/null +++ b/src/nmt/sms.c @@ -0,0 +1,681 @@ +/* NMT SMS (short message service) processing + * + * (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 <time.h> +#include <errno.h> +#include "../common/debug.h" +#include "../common/timer.h" +#include "nmt.h" + +#define SMS_RELEASE_TO 2.0 + +/* TP-Message-Type-Indicator (TP-MTI) */ +#define MTI_SMS_DELIVER 0x00 /* SC -> MS */ +#define MTI_SMS_DELIVER_REPORT 0x00 /* MS -> SC */ +#define MTI_SMS_STATUS_REPORT 0x02 /* SC -> MS */ +#define MTI_SMS_COMMAND 0x02 /* MS -> SC */ +#define MTI_SMS_SUBMIT 0x01 /* MS -> SC */ +#define MTI_SMS_SUBMIT_REPORT 0x01 /* SC -> MS */ +#define MTI_MASK 0x03 /* Bits 0 and 1 */ + +/* TP-More-Messages-to-Send (TP-MMS) */ +#define MMS_NORE 0x00 +#define MMS_NO_MORE 0x04 +#define MMS_MASK 0x04 /* Bit 2 */ + +/* TP-Validity-Period-Format (TP-VPF) */ +#define VPF_NOT_PRESENT 0x00 +#define VPF_PRESENT_INTEGER 0x10 +#define VPF_PRESENT_SEMI_OCTET 0x18 +#define VPF_MASK 0x18 /* Bits 3 and 4 */ + +/* TP-Status-Report-Indication (TP-SRI) */ +#define SRI_NO_REPORT 0x00 +#define SRI_REPORT 0x20 +#define SRI_MASK 0x20 + +/* TP-Status-Report-Request (TP-SRR) */ +#define SSR_NO_REPORT 0x00 +#define SSR_REPORT 0x20 +#define SSR_MASK 0x20 + +/* TP-Failure-Cause (TP-FCS) */ +#define FCS_BUSY 0xc0 +#define FCS_NO_SC_SUBSCRIPTION 0xc1 +#define FSC_SC_SYSTEM_FAILURE 0xC2 +#define FSC_DEST_SME_BARRED 0xC4 +#define FSC_ERROR_IN_MS 0xD2 +#define FSC_MEMORY_EXCEEDED 0xD3 +#define FSC_UNSPECIFIED_ERROR 0xFF + +/* RP-Message-Type-Indicator (RP-MTI) */ +#define RP_MO_DATA 0x00 /* MS -> SC */ +#define RP_MT_DATA 0x01 /* SC -> MS */ +#define RP_MT_ACK 0x02 /* MS -> SC */ +#define RP_MO_ACK 0x03 /* SC -> MS */ +#define RP_MT_ERROR 0x04 /* MS -> SC */ +#define RP_MO_ERROR 0x05 /* SC -> MS */ +#define RP_SM_MEMORY_AVAILABLE 0x06 /* MS -> SC */ +#define RP_SM_READY_TO_RECEIVE 0x07 /* MS -> SC */ +#define RP_SM_NO_MESSAGE 0x07 /* SC -> MS */ +#define RP_MTI_MASK 0x07 + +/* RP IEs */ +#define RP_IE_USER_DATA 0x41 /* wrong in NMT Doc.450-3 1998-04-03 */ +#define RP_IE_CAUSE 0x42 + +/* SC -> MS header */ +static const char sms_header[] = { + 0x01, 0x18, 0x53, 0x4d, 0x53, 0x48, 0x18, 'A', 'B', 'C', 0x02 +}; + +/* + * init and exit + */ + +static void sms_timeout(struct timer *timer); + +/* init instance */ +int sms_init_sender(nmt_t *nmt) +{ + timer_init(&nmt->sms_timer, sms_timeout, nmt); + + return 0; +} + +/* Cleanup transceiver instance. */ +void sms_cleanup_sender(nmt_t *nmt) +{ + timer_exit(&nmt->sms_timer); + sms_reset(nmt); +} + +/* + * send to lower layer + */ + +/* encode header */ +static int encode_header(uint8_t *data) +{ + memcpy(data, sms_header, sizeof(sms_header)); + + return sizeof(sms_header); +} + +/* encode address fields */ +static int encode_address(uint8_t *data, const char *address, uint8_t type, uint8_t plan) +{ + int length = 1; + uint8_t digit; + int i, j; + + PDEBUG(DSMS, DEBUG_DEBUG, "Encode SC->MS header\n"); + + data[length++] = 0x80 | (type << 4) | plan; + j = 0; + for (i = 0; address[i]; i++) { + if (address[i] >= '1' && address[i] <= '9') + digit = address[i] - '0'; + else if (address[i] == '0') + digit = 10; + else if (address[i] == '*') + digit = 11; + else if (address[i] == '#') + digit = 12; + else if (address[i] == '+') + digit = 13; + else + continue; + if ((j & 1) == 0) + data[length] = digit; + else + data[length++] |= digit << 4; + j++; + } + if ((j & 1)) + data[length++] |= 0xf0; + + /* length field: number of semi-octets */ + data[0] = j; + + return length; +} + +/* encode time stamp */ +static int encode_time(uint8_t *data, time_t timestamp) +{ + struct tm *tm = localtime(×tamp); + int length = 0; + uint8_t digit1, digit2; + int quarters, sign; + + PDEBUG(DSMS, DEBUG_DEBUG, "Encode time stamp '%02d.%02d.%02d %02d:%02d:%02d'\n", tm->tm_mday, tm->tm_mon + 1, tm->tm_year % 100, tm->tm_hour, tm->tm_min, tm->tm_sec); + + /* year */ + digit1 = (tm->tm_year % 100) / 10; + if (digit1 == 0) + digit1 = 10; + digit2 = tm->tm_year % 10; + if (digit2 == 0) + digit2 = 10; + data[length++] = (digit2 << 4) | digit1; + + /* month */ + digit1 = (tm->tm_mon + 1) / 10; + if (digit1 == 0) + digit1 = 10; + digit2 = (tm->tm_mon + 1) % 10; + if (digit2 == 0) + digit2 = 10; + data[length++] = (digit2 << 4) | digit1; + + /* day */ + digit1 = tm->tm_mday / 10; + if (digit1 == 0) + digit1 = 10; + digit2 = tm->tm_mday % 10; + if (digit2 == 0) + digit2 = 10; + data[length++] = (digit2 << 4) | digit1; + + /* hour */ + digit1 = tm->tm_hour / 10; + if (digit1 == 0) + digit1 = 10; + digit2 = tm->tm_hour % 10; + if (digit2 == 0) + digit2 = 10; + data[length++] = (digit2 << 4) | digit1; + + /* min */ + digit1 = tm->tm_min / 10; + if (digit1 == 0) + digit1 = 10; + digit2 = tm->tm_min % 10; + if (digit2 == 0) + digit2 = 10; + data[length++] = (digit2 << 4) | digit1; + + /* sec */ + digit1 = tm->tm_sec / 10; + if (digit1 == 0) + digit1 = 10; + digit2 = tm->tm_sec % 10; + if (digit2 == 0) + digit2 = 10; + data[length++] = (digit2 << 4) | digit1; + + /* zone */ + quarters = timezone / 900; + if (quarters < 0) { + quarters = -quarters; + sign = 1; + } else { + quarters = -quarters; + sign = 0; + } + data[length++] = (quarters << 4) | (sign << 3) | (quarters >> 4); + + return length; +} + +/* encode user data */ +static int encode_userdata(uint8_t *data, const char *message) +{ + int length = 1; + char character; + int i, j, pos; + + PDEBUG(DSMS, DEBUG_DEBUG, "Encode user data '%s'\n", message); + + j = 0; + pos = 0; + for (i = 0; message[i]; i++) { + if (message[i] < 128) + character = message[i]; + else + character = '?'; + j++; + if (pos == 0) { + /* character fits and is aligned to the right, new octet */ + data[length] = character; + pos = 7; + } else { + /* character is shifted by pos */ + data[length] |= character << pos; + if (pos > 1) { + /* not all bits fit in octet, so fill the rest to next octet */ + length++; + data[length] = character >> (8 - pos); + pos--; + } else { + /* all bits fit in octet, so go to next octet */ + pos = 0; + length++; + } + } + } + if (pos) + length++; + + /* length field: number of characters */ + data[0] = j; + + return length; +} + +/* deliver SMS (SC->MS) */ +int sms_deliver(nmt_t *nmt, uint8_t ref, const char *orig_address, uint8_t orig_type, uint8_t orig_plan, time_t timestamp, const char *message) +{ + uint8_t data[256], *tpdu_length; + int length = 0; + int orig_len; + int msg_len; + + PDEBUG(DSMS, DEBUG_DEBUG, "Delivering SMS from upper layer\n"); + + orig_len = strlen(orig_address); + msg_len = strlen(message); + + if (orig_len > 24) { + PDEBUG(DSMS, DEBUG_NOTICE, "Originator Address too long (%d characters)\n", orig_len); + return -EINVAL; + } + if (msg_len > 140) { + PDEBUG(DSMS, DEBUG_NOTICE, "Message too long (%d characters)\n", msg_len); + return -EINVAL; + } + + /* HEADER */ + length = encode_header(data); + + /* RP */ + data[length++] = RP_MT_DATA; + data[length++] = ref; + data[length++] = RP_IE_USER_DATA; + tpdu_length = data + length++; + + /* TP */ + data[length++] = MTI_SMS_DELIVER | MMS_NO_MORE | VPF_NOT_PRESENT | SRI_NO_REPORT; + length += encode_address(data + length, orig_address, orig_type, orig_plan); /* TP-OA */ + data[length++] = 0; /* TP-PID */ + data[length++] = 0; /* TP-DCS */ + length += encode_time(data + length, timestamp); + length += encode_userdata(data + length, message); + + /* RP length */ + *tpdu_length = length - (uint8_t)(tpdu_length - data) - 1; + PDEBUG(DSMS, DEBUG_DEBUG, " -> TPDU lenght = %d\n", *tpdu_length); + + nmt->sms.mt = 1; + dms_send(nmt, data, length, 1); + + return 0; +} + +/* report SMS (SC->MS) */ +static void sms_submit_report(nmt_t *nmt, uint8_t ref, int error) +{ + uint8_t data[64]; + int length = 0; + + PDEBUG(DSMS, DEBUG_DEBUG, "Sending Submit Report (%s)\n", (error) ? "error" : "ok"); + + /* HEADER */ + length = encode_header(data); + + /* RP */ + data[length++] = (error) ? RP_MO_ERROR : RP_MO_ACK; + data[length++] = ref; + + dms_send(nmt, data, length, 1); +} + +/* + * receive from lower layer + */ + +/* decdoe message from 8 bit data */ +static void decode_message(const uint8_t *data, int length, char *message) +{ + int fill; + int i; + uint16_t result; + + fill = 0; + result = 0; + for (i = 0; i < length; i++) { + result |= data[i] << fill; + fill += 8; + while (fill >= 7) { + *message++ = result & 0x7f; + result >>= 7; + fill -= 7; + } + } + *message++ = '\0'; +} + +static const char digits2ascii[16] = { + '?', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '0', '*', '#', '+', '?', '?' }; + +static void decode_address(const uint8_t *data, int digits, char *address) +{ + int i; + + for (i = 0; i < digits; i++) { + if (!(i & 1)) + *address++ = digits2ascii[(*data) & 0xf]; + else + *address++ = digits2ascii[(*data++) >> 4]; + } + *address++ = '\0'; +} + +/* decode sms submit message + * return 1 if done, -1 if failed, 0, if more data is required */ +static int decode_sms_submit(nmt_t *nmt, const uint8_t *data, int length) +{ + uint8_t ref, msg_ref; + const uint8_t *orig_data, *tpdu_data, *dest_data, *msg_data; + int orig_len, tpdu_len, dest_len, msg_len; + int orig_digits, dest_digits, msg_chars; + uint8_t orig_type, orig_plan, dest_type, dest_plan; + int tp_vpf_present = 0; + + /* decode ref */ + ref = data[1]; + data += 2; + length -= 2; + + /* do we have originator address length ? */ + if (length < 2) { + PDEBUG(DSMS, DEBUG_DEBUG, "SMS still incomplete, waiting for originator address\n"); + return 0; + } + orig_data = 2 + data; + orig_digits = data[0]; + orig_type = (data[1] >> 4) & 0x7; + orig_plan = data[1] & 0x0f; + orig_len = (orig_digits + 1) >> 1; + if (length < 2 + orig_len) { + PDEBUG(DSMS, DEBUG_DEBUG, "SMS still incomplete, waiting for originator address digits (got %d of %d)\n", length - 1, orig_len); + return 0; + } + data += 2 + orig_len; + length -= 2 + orig_len; + + /* do we have user data IE ? */ + if (length < 2) { + PDEBUG(DSMS, DEBUG_DEBUG, "SMS still incomplete, waiting for user data IE\n"); + return 0; + } + if (data[0] != RP_IE_USER_DATA) { + PDEBUG(DSMS, DEBUG_NOTICE, "missing user data IE\n"); + return -1; + } + tpdu_len = data[1]; + tpdu_data = 2 + data; + if (length < 2 + tpdu_len) { + PDEBUG(DSMS, DEBUG_DEBUG, "SMS still incomplete, waiting for TPDU to be complete\n"); + return 0; + } + data += 2 + tpdu_len; + length -= 2 + tpdu_len; + + /* decode orig address */ + char orig_address[orig_digits + 1]; + decode_address(orig_data, orig_digits, orig_address); + PDEBUG(DSMS, DEBUG_DEBUG, "Decoded originating addess: '%s'\n", orig_address); + + /* go into TP */ + data = tpdu_data; + length = tpdu_len; + + /* check msg_type */ + if (length < 1) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read user data IE\n"); + return -1; + } + if ((data[0] & MTI_MASK) != MTI_SMS_SUBMIT) { + PDEBUG(DSMS, DEBUG_NOTICE, "especting SUBMIT MTI, but got 0x%02x\n", data[0]); + return -1; + } + if ((data[0] & VPF_MASK)) + tp_vpf_present = 1; + data++; + length--; + + /* decode msg ref */ + if (length < 1) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read user data IE\n"); + return -1; + } + msg_ref = data[0]; + data++; + length--; + + /* decode dest address */ + if (length < 2) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read user data IE\n"); + return -1; + } + dest_data = 2 + data; + dest_digits = data[0]; + dest_type = (data[1] >> 4) & 0x7; + dest_plan = data[1] & 0x0f; + dest_len = (dest_digits + 1) >> 1; + if (length < 2 + dest_len) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read user data IE\n"); + return -1; + } + data += 2 + dest_len; + length -= 2 + dest_len; + char dest_address[dest_digits + 1]; + decode_address(dest_data, dest_digits, dest_address); + PDEBUG(DSMS, DEBUG_DEBUG, "Decoded destination addess: '%s'\n", dest_address); + + /* skip above protocol identifier */ + if (length < 1) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read above protocol identifier IE\n"); + return -1; + } + data++; + length--; + + /* decode data coding scheme */ + if (length < 1) { + PDEBUG(DSMS, DEBUG_NOTICE, "short data coding scheme IE\n"); + return -1; + } + if (data[0] != 0) { + PDEBUG(DSMS, DEBUG_NOTICE, "SMS coding unsupported (got 0x%02x)\n", data[0]); + return -1; + } + data++; + length--; + + /* skip validity period */ + if (tp_vpf_present) { + if (length < 1) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read validity period IE\n"); + return -1; + } + data++; + length--; + } + + /* decode data message text */ + if (length < 1) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read user data IE\n"); + return -1; + } + msg_data = data + 1; + msg_chars = data[0]; + msg_len = (msg_chars * 7 + 7) / 8; + if (length < 1 + msg_len) { + PDEBUG(DSMS, DEBUG_NOTICE, "short read user data IE\n"); + return -1; + } + char message[msg_chars + 1]; + decode_message(msg_data, msg_len, message); + PDEBUG(DSMS, DEBUG_DEBUG, "Decoded message: '%s'\n", message); + + sms_submit(nmt, ref, orig_address, orig_type, orig_plan, msg_ref, dest_address, dest_type, dest_plan, message); + + return 1; +} + +/* decode deliver report + * return 1 if done, -1 if failed, 0, if more data is required */ +static int decode_deliver_report(nmt_t *nmt, const uint8_t *data, int length) +{ + uint8_t ref, cause = 0; + int error = 0; + + ref = data[1]; + + if ((data[0] & RP_MTI_MASK) == RP_MT_ERROR) { + error = 1; + if (length < 4) { + PDEBUG(DSMS, DEBUG_DEBUG, "deliver report still incomplete, waiting for cause IE\n"); + return 0; + } + if (length < 4 + data[3]) { + PDEBUG(DSMS, DEBUG_DEBUG, "deliver report still incomplete, waiting for cause IE content\n"); + return 0; + } + if (data[2] == RP_IE_CAUSE && data[3] > 0) + cause = data[4]; + PDEBUG(DSMS, DEBUG_DEBUG, "Decoded delivery report: ERROR, cause=%d\n", cause); + } else + PDEBUG(DSMS, DEBUG_DEBUG, "Decoded delivery report: OK\n"); + + sms_deliver_report(nmt, ref, error, cause); + + return 1; +} + +/* receive from DMS layer */ +void dms_receive(nmt_t *nmt, const uint8_t *data, int length, int eight_bits) +{ + sms_t *sms = &nmt->sms; + int space; + int rc = 0; + char debug_text[length * 5 + 1]; + int i; + + for (i = 0; i < length; i++) + sprintf(debug_text + i * 5, " 0x%02x", data[i]); + debug_text[length * 5] = '\0'; + + PDEBUG(DSMS, DEBUG_DEBUG, "Received %d bytes from DMS layer:%s\n", length, debug_text); + + if (sms->mt && !sms->data_sent) { + PDEBUG(DSMS, DEBUG_NOTICE, "Ignoring data while we transmit data\n"); + return; + } + + /* append received data */ + space = sizeof(sms->rx_buffer) - sms->rx_count; + if (space < length) { + PDEBUG(DSMS, DEBUG_NOTICE, "Received message exceeds RX buffer, terminating call!\n"); +release: + timer_start(&nmt->sms_timer, SMS_RELEASE_TO); + return; + } + memcpy(sms->rx_buffer + sms->rx_count, data, length); + sms->rx_count += length; + + /* go into buffer */ + data = sms->rx_buffer; + length = sms->rx_count; + + /* check if complete */ + if (length < 2) + return; + switch (data[0] & RP_MTI_MASK) { + case RP_MT_ACK: + rc = decode_deliver_report(nmt, data, length); + break; + case RP_MT_ERROR: + rc = decode_deliver_report(nmt, data, length); + break; + case RP_MO_DATA: + rc = decode_sms_submit(nmt, data, length); + if (rc < 0) + sms_submit_report(nmt, data[1], FSC_UNSPECIFIED_ERROR); + else if (rc > 0) { + sms_submit_report(nmt, data[1], 0); + } + /* no release, we release afeter the report */ + rc = 0; + break; + case RP_SM_READY_TO_RECEIVE: + PDEBUG(DSMS, DEBUG_NOTICE, "Received READY-TO-RECEVIE message.\n"); + data += length; + length -= length; + break; + default: + PDEBUG(DSMS, DEBUG_NOTICE, "Received unknown RP message type %d.\n", data[0]); + rc = -1; + } + if (rc) + goto release; + + return; +} + +static void sms_timeout(struct timer *timer) +{ + nmt_t *nmt = (nmt_t *)timer->priv; + + sms_release(nmt); +} + +/* all data has been sent to mobile */ +void dms_all_sent(nmt_t *nmt) +{ + sms_t *sms = &nmt->sms; + + if (!sms->data_sent) { + if (!sms->mt) { + PDEBUG(DSMS, DEBUG_DEBUG, "Done sending submit report, releasing.\n"); + timer_start(&nmt->sms_timer, SMS_RELEASE_TO); + } + sms->data_sent = 1; + PDEBUG(DSMS, DEBUG_DEBUG, "DMS layer indicates acknowledge of sent data\n"); + } +} + +void sms_reset(nmt_t *nmt) +{ + sms_t *sms = &nmt->sms; + + PDEBUG(DSMS, DEBUG_DEBUG, "Resetting SMS states\n"); + timer_stop(&nmt->sms_timer); + + memset(sms, 0, sizeof(*sms)); +} + diff --git a/src/nmt/sms.h b/src/nmt/sms.h new file mode 100644 index 0000000..f7fcc10 --- /dev/null +++ b/src/nmt/sms.h @@ -0,0 +1,34 @@ + +#define SMS_TYPE_UKNOWN 0x0 +#define SMS_TYPE_INTERNATIONAL 0x1 +#define SMS_TYPE_NATIONAL 0x2 +#define SMS_TYPE_NETWORK 0x3 +#define SMS_TYPE_SUBSCRIBER 0x4 +#define SMS_TYPE_ALPHANUMERIC 0x5 +#define SMS_TYPE_ABBREVIATED 0x6 +#define SMS_TYPE_RESERVED 0x7 + +#define SMS_PLAN_UNKOWN 0x0 +#define SMS_PLAN_ISDN_TEL 0x1 +#define SMS_PLAN_DATA 0x3 +#define SMS_PLAN_TELEX 0x4 +#define SMS_PLAN_NATIONAL 0x8 +#define SMS_PLAN_PRIVATE 0x9 +#define SMS_PLAN_ERMES 0xa +#define SMS_PLAN_RESERVED 0xf + +typedef struct sms { + uint8_t rx_buffer[1024]; /* data received from MS */ + int rx_count; /* number of bytes in buffer */ + int data_sent; /* all pending data have been sent and was acked */ + int mt; /* mobile terminating SMS */ +} sms_t; + +int sms_init_sender(nmt_t *nmt); +void sms_cleanup_sender(nmt_t *nmt); +void sms_submit(nmt_t *nmt, uint8_t ref, const char *orig_address, uint8_t orig_type, uint8_t orig_plan, int msg_ref, const char *dest_address, uint8_t dest_type, uint8_t dest_plan, const char *message); +void sms_deliver_report(nmt_t *nmt, uint8_t ref, int error, uint8_t cause); +int sms_deliver(nmt_t *nmt, uint8_t ref, const char *orig_address, uint8_t type, uint8_t plan, time_t timestamp, const char *message); +void sms_release(nmt_t *nmt); +void sms_reset(nmt_t *nmt); + |