aboutsummaryrefslogtreecommitdiffstats
path: root/src/nmt
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2016-07-04 19:52:00 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2016-07-09 15:16:16 +0200
commite7fa08b6dfdfb607264179b457276d18c24da269 (patch)
tree1f51334dfeccb6cda2dd0671b53d648418e88b8a /src/nmt
parent64c829909bc5b73a7765272ad07cf6465762aeac (diff)
NMT / SMS: Short Message Service support
Diffstat (limited to 'src/nmt')
-rw-r--r--src/nmt/Makefile.am1
-rw-r--r--src/nmt/main.c68
-rw-r--r--src/nmt/nmt.c98
-rw-r--r--src/nmt/nmt.h10
-rw-r--r--src/nmt/sms.c681
-rw-r--r--src/nmt/sms.h34
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(&timestamp);
+ 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);
+