aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2017-07-04 23:08:44 +0200
committerNeels Hofmeyr <nhofmeyr@sysmocom.de>2017-07-12 23:42:44 +0200
commit0b5cf1d1d40815b5b910330074ab4e234014448a (patch)
tree77eed5bde035b276b63f92c0f23e944049e59897 /src/libmsc
parent9e3c66b1814246f6c06a6f78975f54dfe9e2cf8c (diff)
move openbsc/* to repos root
This is the first step in creating this repository from the legacy openbsc.git. Like all other Osmocom repositories, keep the autoconf and automake files in the repository root. openbsc.git has been the sole exception, which ends now. Change-Id: I9c6f2a448d9cb1cc088cf1cf6918b69d7e69b4e7
Diffstat (limited to 'src/libmsc')
-rw-r--r--src/libmsc/Makefile.am58
-rw-r--r--src/libmsc/auth.c157
-rw-r--r--src/libmsc/ctrl_commands.c212
-rw-r--r--src/libmsc/db.c1752
-rw-r--r--src/libmsc/gsm_04_08.c4047
-rw-r--r--src/libmsc/gsm_04_11.c1070
-rw-r--r--src/libmsc/gsm_04_80.c155
-rw-r--r--src/libmsc/gsm_subscriber.c422
-rw-r--r--src/libmsc/meas_feed.c167
-rw-r--r--src/libmsc/meas_feed.h12
-rw-r--r--src/libmsc/mncc.c107
-rw-r--r--src/libmsc/mncc_builtin.c426
-rw-r--r--src/libmsc/mncc_sock.c318
-rw-r--r--src/libmsc/osmo_msc.c177
-rw-r--r--src/libmsc/rrlp.c104
-rw-r--r--src/libmsc/silent_call.c152
-rw-r--r--src/libmsc/smpp_openbsc.c758
-rw-r--r--src/libmsc/smpp_smsc.c1030
-rw-r--r--src/libmsc/smpp_smsc.h166
-rw-r--r--src/libmsc/smpp_utils.c62
-rw-r--r--src/libmsc/smpp_vty.c612
-rw-r--r--src/libmsc/sms_queue.c544
-rw-r--r--src/libmsc/token_auth.c160
-rw-r--r--src/libmsc/transaction.c163
-rw-r--r--src/libmsc/ussd.c95
-rw-r--r--src/libmsc/vty_interface_layer3.c1210
26 files changed, 14136 insertions, 0 deletions
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
new file mode 100644
index 000000000..9d966dbc1
--- /dev/null
+++ b/src/libmsc/Makefile.am
@@ -0,0 +1,58 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBCRYPTO_CFLAGS) \
+ $(LIBSMPP34_CFLAGS) \
+ $(NULL)
+
+noinst_HEADERS = \
+ meas_feed.h \
+ $(NULL)
+
+noinst_LIBRARIES = \
+ libmsc.a \
+ $(NULL)
+
+libmsc_a_SOURCES = \
+ auth.c \
+ db.c \
+ gsm_04_08.c \
+ gsm_04_11.c \
+ gsm_04_80.c \
+ gsm_subscriber.c \
+ mncc.c \
+ mncc_builtin.c \
+ mncc_sock.c \
+ rrlp.c \
+ silent_call.c \
+ sms_queue.c \
+ token_auth.c \
+ ussd.c \
+ vty_interface_layer3.c \
+ transaction.c \
+ osmo_msc.c \
+ ctrl_commands.c \
+ meas_feed.c \
+ $(NULL)
+
+if BUILD_SMPP
+noinst_HEADERS += \
+ smpp_smsc.h \
+ $(NULL)
+
+libmsc_a_SOURCES += \
+ smpp_smsc.c \
+ smpp_openbsc.c \
+ smpp_vty.c \
+ smpp_utils.c \
+ $(NULL)
+endif
diff --git a/src/libmsc/auth.c b/src/libmsc/auth.c
new file mode 100644
index 000000000..19def1ec1
--- /dev/null
+++ b/src/libmsc/auth.c
@@ -0,0 +1,157 @@
+/* Authentication related functions */
+
+/*
+ * (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/auth.h>
+#include <openbsc/gsm_data.h>
+
+#include <osmocom/gsm/comp128.h>
+#include <osmocom/core/utils.h>
+
+#include <openssl/rand.h>
+
+#include <stdlib.h>
+
+const struct value_string auth_action_names[] = {
+ OSMO_VALUE_STRING(AUTH_ERROR),
+ OSMO_VALUE_STRING(AUTH_NOT_AVAIL),
+ OSMO_VALUE_STRING(AUTH_DO_AUTH_THEN_CIPH),
+ OSMO_VALUE_STRING(AUTH_DO_CIPH),
+ OSMO_VALUE_STRING(AUTH_DO_AUTH),
+ { 0, NULL }
+};
+
+static int
+_use_xor(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+ int i, l = ainfo->a3a8_ki_len;
+
+ if ((l > A38_XOR_MAX_KEY_LEN) || (l < A38_XOR_MIN_KEY_LEN)) {
+ LOGP(DMM, LOGL_ERROR, "Invalid XOR key (len=%d) %s\n",
+ ainfo->a3a8_ki_len,
+ osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+ return -1;
+ }
+
+ for (i=0; i<4; i++)
+ atuple->vec.sres[i] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i];
+ for (i=4; i<12; i++)
+ atuple->vec.kc[i-4] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i];
+
+ return 0;
+}
+
+static int
+_use_comp128_v1(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+ if (ainfo->a3a8_ki_len != A38_COMP128_KEY_LEN) {
+ LOGP(DMM, LOGL_ERROR, "Invalid COMP128v1 key (len=%d) %s\n",
+ ainfo->a3a8_ki_len,
+ osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+ return -1;
+ }
+
+ comp128(ainfo->a3a8_ki, atuple->vec.rand, atuple->vec.sres, atuple->vec.kc);
+
+ return 0;
+}
+
+/* Return values
+ * -1 -> Internal error
+ * 0 -> Not available
+ * 1 -> Tuple returned, need to do auth, then enable cipher
+ * 2 -> Tuple returned, need to enable cipher
+ */
+int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple,
+ struct gsm_subscriber *subscr, int key_seq)
+{
+ struct gsm_auth_info ainfo;
+ int rc;
+
+ /* Get subscriber info (if any) */
+ rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+ if (rc < 0) {
+ LOGP(DMM, LOGL_NOTICE,
+ "No retrievable Ki for subscriber %s, skipping auth\n",
+ subscr_name(subscr));
+ return rc == -ENOENT ? AUTH_NOT_AVAIL : AUTH_ERROR;
+ }
+
+ /* If possible, re-use the last tuple and skip auth */
+ rc = db_get_lastauthtuple_for_subscr(atuple, subscr);
+ if ((rc == 0) &&
+ (key_seq != GSM_KEY_SEQ_INVAL) &&
+ (key_seq == atuple->key_seq) &&
+ (atuple->use_count < 3))
+ {
+ atuple->use_count++;
+ db_sync_lastauthtuple_for_subscr(atuple, subscr);
+ DEBUGP(DMM, "Auth tuple use < 3, just doing ciphering\n");
+ return AUTH_DO_CIPH;
+ }
+
+ /* Generate a new one */
+ if (rc != 0) {
+ /* If db_get_lastauthtuple_for_subscr() returned nothing, make
+ * sure the atuple memory is initialized to zero and thus start
+ * off with key_seq = 0. */
+ memset(atuple, 0, sizeof(*atuple));
+ } else {
+ /* If db_get_lastauthtuple_for_subscr() returned a previous
+ * tuple, use the next key_seq. */
+ atuple->key_seq = (atuple->key_seq + 1) % 7;
+ }
+ atuple->use_count = 1;
+
+ if (RAND_bytes(atuple->vec.rand, sizeof(atuple->vec.rand)) != 1) {
+ LOGP(DMM, LOGL_NOTICE, "RAND_bytes failed, can't generate new auth tuple\n");
+ return AUTH_ERROR;
+ }
+
+ switch (ainfo.auth_algo) {
+ case AUTH_ALGO_NONE:
+ DEBUGP(DMM, "No authentication for subscriber\n");
+ return AUTH_NOT_AVAIL;
+
+ case AUTH_ALGO_XOR:
+ if (_use_xor(&ainfo, atuple))
+ return AUTH_NOT_AVAIL;
+ break;
+
+ case AUTH_ALGO_COMP128v1:
+ if (_use_comp128_v1(&ainfo, atuple))
+ return AUTH_NOT_AVAIL;
+ break;
+
+ default:
+ DEBUGP(DMM, "Unsupported auth type algo_id=%d\n",
+ ainfo.auth_algo);
+ return AUTH_NOT_AVAIL;
+ }
+
+ db_sync_lastauthtuple_for_subscr(atuple, subscr);
+
+ DEBUGP(DMM, "Need to do authentication and ciphering\n");
+ return AUTH_DO_AUTH_THEN_CIPH;
+}
+
diff --git a/src/libmsc/ctrl_commands.c b/src/libmsc/ctrl_commands.c
new file mode 100644
index 000000000..c99dde44c
--- /dev/null
+++ b/src/libmsc/ctrl_commands.c
@@ -0,0 +1,212 @@
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ * (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+
+#include <stdbool.h>
+
+static bool alg_supported(const char *alg)
+{
+ /*
+ * TODO: share this with the vty_interface and extend to all
+ * algorithms supported by libosmocore now. Make it table based
+ * as well.
+ */
+ if (strcasecmp(alg, "none") == 0)
+ return true;
+ if (strcasecmp(alg, "xor") == 0)
+ return true;
+ if (strcasecmp(alg, "comp128v1") == 0)
+ return true;
+ return false;
+}
+
+static int verify_subscriber_modify(struct ctrl_cmd *cmd, const char *value, void *d)
+{
+ char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
+ int rc = 0;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ imsi = strtok_r(tmp, ",", &saveptr);
+ msisdn = strtok_r(NULL, ",", &saveptr);
+ alg = strtok_r(NULL, ",", &saveptr);
+ ki = strtok_r(NULL, ",", &saveptr);
+
+ if (!imsi || !msisdn)
+ rc = 1;
+ else if (strlen(imsi) > GSM23003_IMSI_MAX_DIGITS)
+ rc = 1;
+ else if (strlen(msisdn) >= GSM_EXTENSION_LENGTH)
+ rc = 1;
+ else if (alg) {
+ if (!alg_supported(alg))
+ rc = 1;
+ else if (strcasecmp(alg, "none") != 0 && !ki)
+ rc = 1;
+ }
+
+ talloc_free(tmp);
+ return rc;
+}
+
+static int set_subscriber_modify(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
+ struct gsm_subscriber* subscr;
+ int rc;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ return 1;
+
+ imsi = strtok_r(tmp, ",", &saveptr);
+ msisdn = strtok_r(NULL, ",", &saveptr);
+ alg = strtok_r(NULL, ",", &saveptr);
+ ki = strtok_r(NULL, ",", &saveptr);
+
+ subscr = subscr_get_by_imsi(net->subscr_group, imsi);
+ if (!subscr)
+ subscr = subscr_create_subscriber(net->subscr_group, imsi);
+ if (!subscr)
+ goto fail;
+
+ subscr->authorized = 1;
+ osmo_strlcpy(subscr->extension, msisdn, sizeof(subscr->extension));
+
+ /* put it back to the db */
+ rc = db_sync_subscriber(subscr);
+ db_subscriber_update(subscr);
+
+ /* handle optional ciphering */
+ if (alg) {
+ if (strcasecmp(alg, "none") == 0)
+ db_sync_authinfo_for_subscr(NULL, subscr);
+ else {
+ struct gsm_auth_info ainfo = { 0, };
+ /* the verify should make sure that this is okay */
+ OSMO_ASSERT(alg);
+ OSMO_ASSERT(ki);
+
+ if (strcasecmp(alg, "xor") == 0)
+ ainfo.auth_algo = AUTH_ALGO_XOR;
+ else if (strcasecmp(alg, "comp128v1") == 0)
+ ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+
+ rc = osmo_hexparse(ki, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+ if (rc < 0) {
+ subscr_put(subscr);
+ talloc_free(tmp);
+ cmd->reply = "Failed to parse KI";
+ return CTRL_CMD_ERROR;
+ }
+
+ ainfo.a3a8_ki_len = rc;
+ db_sync_authinfo_for_subscr(&ainfo, subscr);
+ rc = 0;
+ }
+ db_sync_lastauthtuple_for_subscr(NULL, subscr);
+ }
+ subscr_put(subscr);
+
+ talloc_free(tmp);
+
+ if (rc != 0) {
+ cmd->reply = "Failed to store the record in the DB";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+
+fail:
+ talloc_free(tmp);
+ cmd->reply = "Failed to create subscriber";
+ return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_WO(subscriber_modify, "subscriber-modify-v1");
+
+static int set_subscriber_delete(struct ctrl_cmd *cmd, void *data)
+{
+ int was_used = 0;
+ int rc;
+ struct gsm_subscriber *subscr;
+ struct gsm_network *net = cmd->node;
+
+ subscr = subscr_get_by_imsi(net->subscr_group, cmd->value);
+ if (!subscr) {
+ cmd->reply = "Failed to find subscriber";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (subscr->use_count != 1) {
+ LOGP(DCTRL, LOGL_NOTICE, "Going to remove active subscriber.\n");
+ was_used = 1;
+ }
+
+ rc = db_subscriber_delete(subscr);
+ subscr_put(subscr);
+
+ if (rc != 0) {
+ cmd->reply = "Failed to remove subscriber";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = was_used ? "Removed active subscriber" : "Removed";
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_WO_NOVRF(subscriber_delete, "subscriber-delete-v1");
+
+static void list_cb(struct gsm_subscriber *subscr, void *d)
+{
+ char **data = (char **) d;
+ *data = talloc_asprintf_append(*data, "%s,%s\n",
+ subscr->imsi, subscr->extension);
+}
+
+static int get_subscriber_list(struct ctrl_cmd *cmd, void *d)
+{
+ cmd->reply = talloc_strdup(cmd, "");
+
+ db_subscriber_list_active(list_cb, &cmd->reply);
+ printf("%s\n", cmd->reply);
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(subscriber_list, "subscriber-list-active-v1");
+
+int msc_ctrl_cmds_install(void)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_modify);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_delete);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_list);
+ return rc;
+}
diff --git a/src/libmsc/db.c b/src/libmsc/db.c
new file mode 100644
index 000000000..5fe2a3c6b
--- /dev/null
+++ b/src/libmsc/db.c
@@ -0,0 +1,1752 @@
+/* Simple HLR/VLR database backend using dbi */
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <dbi/dbi.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/utils.h>
+
+#include <openssl/rand.h>
+
+/* Semi-Private-Interface (SPI) for the subscriber code */
+void subscr_direct_free(struct gsm_subscriber *subscr);
+
+static char *db_basename = NULL;
+static char *db_dirname = NULL;
+static dbi_conn conn;
+
+#define SCHEMA_REVISION "4"
+
+enum {
+ SCHEMA_META,
+ INSERT_META,
+ SCHEMA_SUBSCRIBER,
+ SCHEMA_AUTH,
+ SCHEMA_EQUIPMENT,
+ SCHEMA_EQUIPMENT_WATCH,
+ SCHEMA_SMS,
+ SCHEMA_VLR,
+ SCHEMA_APDU,
+ SCHEMA_COUNTERS,
+ SCHEMA_RATE,
+ SCHEMA_AUTHKEY,
+ SCHEMA_AUTHLAST,
+};
+
+static const char *create_stmts[] = {
+ [SCHEMA_META] = "CREATE TABLE IF NOT EXISTS Meta ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "key TEXT UNIQUE NOT NULL, "
+ "value TEXT NOT NULL"
+ ")",
+ [INSERT_META] = "INSERT OR IGNORE INTO Meta "
+ "(key, value) "
+ "VALUES "
+ "('revision', " SCHEMA_REVISION ")",
+ [SCHEMA_SUBSCRIBER] = "CREATE TABLE IF NOT EXISTS Subscriber ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "created TIMESTAMP NOT NULL, "
+ "updated TIMESTAMP NOT NULL, "
+ "imsi NUMERIC UNIQUE NOT NULL, "
+ "name TEXT, "
+ "extension TEXT UNIQUE, "
+ "authorized INTEGER NOT NULL DEFAULT 0, "
+ "tmsi TEXT UNIQUE, "
+ "lac INTEGER NOT NULL DEFAULT 0, "
+ "expire_lu TIMESTAMP DEFAULT NULL"
+ ")",
+ [SCHEMA_AUTH] = "CREATE TABLE IF NOT EXISTS AuthToken ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "subscriber_id INTEGER UNIQUE NOT NULL, "
+ "created TIMESTAMP NOT NULL, "
+ "token TEXT UNIQUE NOT NULL"
+ ")",
+ [SCHEMA_EQUIPMENT] = "CREATE TABLE IF NOT EXISTS Equipment ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "created TIMESTAMP NOT NULL, "
+ "updated TIMESTAMP NOT NULL, "
+ "name TEXT, "
+ "classmark1 NUMERIC, "
+ "classmark2 BLOB, "
+ "classmark3 BLOB, "
+ "imei NUMERIC UNIQUE NOT NULL"
+ ")",
+ [SCHEMA_EQUIPMENT_WATCH] = "CREATE TABLE IF NOT EXISTS EquipmentWatch ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "created TIMESTAMP NOT NULL, "
+ "updated TIMESTAMP NOT NULL, "
+ "subscriber_id NUMERIC NOT NULL, "
+ "equipment_id NUMERIC NOT NULL, "
+ "UNIQUE (subscriber_id, equipment_id) "
+ ")",
+ [SCHEMA_SMS] = "CREATE TABLE IF NOT EXISTS SMS ("
+ /* metadata, not part of sms */
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "created TIMESTAMP NOT NULL, "
+ "sent TIMESTAMP, "
+ "deliver_attempts INTEGER NOT NULL DEFAULT 0, "
+ /* data directly copied/derived from SMS */
+ "valid_until TIMESTAMP, "
+ "reply_path_req INTEGER NOT NULL, "
+ "status_rep_req INTEGER NOT NULL, "
+ "protocol_id INTEGER NOT NULL, "
+ "data_coding_scheme INTEGER NOT NULL, "
+ "ud_hdr_ind INTEGER NOT NULL, "
+ "src_addr TEXT NOT NULL, "
+ "src_ton INTEGER NOT NULL, "
+ "src_npi INTEGER NOT NULL, "
+ "dest_addr TEXT NOT NULL, "
+ "dest_ton INTEGER NOT NULL, "
+ "dest_npi INTEGER NOT NULL, "
+ "user_data BLOB, " /* TP-UD */
+ /* additional data, interpreted from SMS */
+ "header BLOB, " /* UD Header */
+ "text TEXT " /* decoded UD after UDH */
+ ")",
+ [SCHEMA_VLR] = "CREATE TABLE IF NOT EXISTS VLR ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "created TIMESTAMP NOT NULL, "
+ "updated TIMESTAMP NOT NULL, "
+ "subscriber_id NUMERIC UNIQUE NOT NULL, "
+ "last_bts NUMERIC NOT NULL "
+ ")",
+ [SCHEMA_APDU] = "CREATE TABLE IF NOT EXISTS ApduBlobs ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "created TIMESTAMP NOT NULL, "
+ "apdu_id_flags INTEGER NOT NULL, "
+ "subscriber_id INTEGER NOT NULL, "
+ "apdu BLOB "
+ ")",
+ [SCHEMA_COUNTERS] = "CREATE TABLE IF NOT EXISTS Counters ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "timestamp TIMESTAMP NOT NULL, "
+ "value INTEGER NOT NULL, "
+ "name TEXT NOT NULL "
+ ")",
+ [SCHEMA_RATE] = "CREATE TABLE IF NOT EXISTS RateCounters ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "timestamp TIMESTAMP NOT NULL, "
+ "value INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "idx INTEGER NOT NULL "
+ ")",
+ [SCHEMA_AUTHKEY] = "CREATE TABLE IF NOT EXISTS AuthKeys ("
+ "subscriber_id INTEGER PRIMARY KEY, "
+ "algorithm_id INTEGER NOT NULL, "
+ "a3a8_ki BLOB "
+ ")",
+ [SCHEMA_AUTHLAST] = "CREATE TABLE IF NOT EXISTS AuthLastTuples ("
+ "subscriber_id INTEGER PRIMARY KEY, "
+ "issued TIMESTAMP NOT NULL, "
+ "use_count INTEGER NOT NULL DEFAULT 0, "
+ "key_seq INTEGER NOT NULL, "
+ "rand BLOB NOT NULL, "
+ "sres BLOB NOT NULL, "
+ "kc BLOB NOT NULL "
+ ")",
+};
+
+void db_error_func(dbi_conn conn, void *data)
+{
+ const char *msg;
+ dbi_conn_error(conn, &msg);
+ LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg);
+ osmo_log_backtrace(DDB, LOGL_ERROR);
+}
+
+static int update_db_revision_2(void)
+{
+ dbi_result result;
+
+ result = dbi_conn_query(conn,
+ "ALTER TABLE Subscriber "
+ "ADD COLUMN expire_lu "
+ "TIMESTAMP DEFAULT NULL");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to alter table Subscriber (upgrade from rev 2).\n");
+ return -EINVAL;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_query(conn,
+ "UPDATE Meta "
+ "SET value = '3' "
+ "WHERE key = 'revision'");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to update DB schema revision (upgrade from rev 2).\n");
+ return -EINVAL;
+ }
+ dbi_result_free(result);
+
+ return 0;
+}
+
+/**
+ * Copied from the normal sms_from_result_v3 to avoid having
+ * to make sure that the real routine will remain backward
+ * compatible.
+ */
+static struct gsm_sms *sms_from_result_v3(dbi_result result)
+{
+ struct gsm_sms *sms = sms_alloc();
+ long long unsigned int sender_id;
+ struct gsm_subscriber *sender;
+ const char *text, *daddr;
+ const unsigned char *user_data;
+ char buf[32];
+
+ if (!sms)
+ return NULL;
+
+ sms->id = dbi_result_get_ulonglong(result, "id");
+
+ sender_id = dbi_result_get_ulonglong(result, "sender_id");
+ snprintf(buf, sizeof(buf), "%llu", sender_id);
+ sender = db_get_subscriber(GSM_SUBSCRIBER_ID, buf);
+ OSMO_ASSERT(sender);
+ osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr));
+ subscr_direct_free(sender);
+ sender = NULL;
+
+ sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
+ sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
+ sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
+ sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
+ sms->data_coding_scheme = dbi_result_get_ulonglong(result,
+ "data_coding_scheme");
+
+ daddr = dbi_result_get_string(result, "dest_addr");
+ if (daddr)
+ osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
+
+ sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+ user_data = dbi_result_get_binary(result, "user_data");
+ if (sms->user_data_len > sizeof(sms->user_data))
+ sms->user_data_len = (uint8_t) sizeof(sms->user_data);
+ memcpy(sms->user_data, user_data, sms->user_data_len);
+
+ text = dbi_result_get_string(result, "text");
+ if (text)
+ osmo_strlcpy(sms->text, text, sizeof(sms->text));
+ return sms;
+}
+
+static int update_db_revision_3(void)
+{
+ dbi_result result;
+ struct gsm_sms *sms;
+
+ LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 3\n");
+
+ result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to begin transaction (upgrade from rev 3)\n");
+ return -EINVAL;
+ }
+ dbi_result_free(result);
+
+ /* Rename old SMS table to be able create a new one */
+ result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_3");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to rename the old SMS table (upgrade from rev 3).\n");
+ goto rollback;
+ }
+ dbi_result_free(result);
+
+ /* Create new SMS table with all the bells and whistles! */
+ result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to create a new SMS table (upgrade from rev 3).\n");
+ goto rollback;
+ }
+ dbi_result_free(result);
+
+ /* Cycle through old messages and convert them to the new format */
+ result = dbi_conn_query(conn, "SELECT * FROM SMS_3");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed fetch messages from the old SMS table (upgrade from rev 3).\n");
+ goto rollback;
+ }
+ while (dbi_result_next_row(result)) {
+ sms = sms_from_result_v3(result);
+ if (db_sms_store(sms) != 0) {
+ LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 3).\n");
+ sms_free(sms);
+ dbi_result_free(result);
+ goto rollback;
+ }
+ sms_free(sms);
+ }
+ dbi_result_free(result);
+
+ /* Remove the temporary table */
+ result = dbi_conn_query(conn, "DROP TABLE SMS_3");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to drop the old SMS table (upgrade from rev 3).\n");
+ goto rollback;
+ }
+ dbi_result_free(result);
+
+ /* We're done. Bump DB Meta revision to 4 */
+ result = dbi_conn_query(conn,
+ "UPDATE Meta "
+ "SET value = '4' "
+ "WHERE key = 'revision'");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to update DB schema revision (upgrade from rev 3).\n");
+ goto rollback;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_query(conn, "COMMIT TRANSACTION");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to commit the transaction (upgrade from rev 3)\n");
+ return -EINVAL;
+ } else {
+ dbi_result_free(result);
+ }
+
+ /* Shrink DB file size by actually wiping out SMS_3 table data */
+ result = dbi_conn_query(conn, "VACUUM");
+ if (!result)
+ LOGP(DDB, LOGL_ERROR,
+ "VACUUM failed. Ignoring it (upgrade from rev 3).\n");
+ else
+ dbi_result_free(result);
+
+ return 0;
+
+rollback:
+ result = dbi_conn_query(conn, "ROLLBACK TRANSACTION");
+ if (!result)
+ LOGP(DDB, LOGL_ERROR,
+ "Rollback failed (upgrade from rev 3).\n");
+ else
+ dbi_result_free(result);
+ return -EINVAL;
+}
+
+static int check_db_revision(void)
+{
+ dbi_result result;
+ const char *rev_s;
+ int db_rev = 0;
+
+ /* Make a query */
+ result = dbi_conn_query(conn,
+ "SELECT value FROM Meta "
+ "WHERE key = 'revision'");
+
+ if (!result)
+ return -EINVAL;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return -EINVAL;
+ }
+
+ /* Fetch the DB schema revision */
+ rev_s = dbi_result_get_string(result, "value");
+ if (!rev_s) {
+ dbi_result_free(result);
+ return -EINVAL;
+ }
+
+ if (!strcmp(rev_s, SCHEMA_REVISION)) {
+ /* Everything is fine */
+ dbi_result_free(result);
+ return 0;
+ }
+
+ db_rev = atoi(rev_s);
+ dbi_result_free(result);
+
+ /* Incremental migration waterfall */
+ switch (db_rev) {
+ case 2:
+ if (update_db_revision_2())
+ goto error;
+ case 3:
+ if (update_db_revision_3())
+ goto error;
+
+ /* The end of waterfall */
+ break;
+ default:
+ LOGP(DDB, LOGL_FATAL,
+ "Invalid database schema revision '%d'.\n", db_rev);
+ return -EINVAL;
+ }
+
+ return 0;
+
+error:
+ LOGP(DDB, LOGL_FATAL, "Failed to update database "
+ "from schema revision '%d'.\n", db_rev);
+ return -EINVAL;
+}
+
+static int db_configure(void)
+{
+ dbi_result result;
+
+ result = dbi_conn_query(conn,
+ "PRAGMA synchronous = FULL");
+ if (!result)
+ return -EINVAL;
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_init(const char *name)
+{
+ dbi_initialize(NULL);
+
+ conn = dbi_conn_new("sqlite3");
+ if (conn == NULL) {
+ LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n");
+ return 1;
+ }
+
+ dbi_conn_error_handler( conn, db_error_func, NULL );
+
+ /* MySQL
+ dbi_conn_set_option(conn, "host", "localhost");
+ dbi_conn_set_option(conn, "username", "your_name");
+ dbi_conn_set_option(conn, "password", "your_password");
+ dbi_conn_set_option(conn, "dbname", "your_dbname");
+ dbi_conn_set_option(conn, "encoding", "UTF-8");
+ */
+
+ /* SqLite 3 */
+ db_basename = strdup(name);
+ db_dirname = strdup(name);
+ dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname));
+ dbi_conn_set_option(conn, "dbname", basename(db_basename));
+
+ if (dbi_conn_connect(conn) < 0)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ free(db_dirname);
+ free(db_basename);
+ db_dirname = db_basename = NULL;
+ return -1;
+}
+
+
+int db_prepare(void)
+{
+ dbi_result result;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+ result = dbi_conn_query(conn, create_stmts[i]);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to create some table.\n");
+ return 1;
+ }
+ dbi_result_free(result);
+ }
+
+ if (check_db_revision() < 0) {
+ LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, "
+ "please update your database schema\n");
+ return -1;
+ }
+
+ db_configure();
+
+ return 0;
+}
+
+int db_fini(void)
+{
+ dbi_conn_close(conn);
+ dbi_shutdown();
+
+ free(db_dirname);
+ free(db_basename);
+ return 0;
+}
+
+struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
+ uint64_t smax, bool alloc_exten)
+{
+ dbi_result result;
+ struct gsm_subscriber *subscr;
+
+ /* Is this subscriber known in the db? */
+ subscr = db_get_subscriber(GSM_SUBSCRIBER_IMSI, imsi);
+ if (subscr) {
+ subscr_put(subscr);
+ return NULL;
+ }
+
+ subscr = subscr_alloc();
+ if (!subscr)
+ return NULL;
+ subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO Subscriber "
+ "(imsi, created, updated) "
+ "VALUES "
+ "(%s, datetime('now'), datetime('now')) ",
+ imsi
+ );
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n");
+ subscr_put(subscr);
+ return NULL;
+ }
+ subscr->id = dbi_conn_sequence_last(conn, NULL);
+ osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi));
+ dbi_result_free(result);
+ LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
+ if (alloc_exten)
+ db_subscriber_alloc_exten(subscr, smin, smax);
+ return subscr;
+}
+
+osmo_static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size);
+
+static int get_equipment_by_subscr(struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+ const char *string;
+ unsigned char cm1;
+ const unsigned char *cm2, *cm3;
+ struct gsm_equipment *equip = &subscr->equipment;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT Equipment.* "
+ "FROM Equipment JOIN EquipmentWatch ON "
+ "EquipmentWatch.equipment_id=Equipment.id "
+ "WHERE EquipmentWatch.subscriber_id = %llu "
+ "ORDER BY EquipmentWatch.updated DESC", subscr->id);
+ if (!result)
+ return -EIO;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return -ENOENT;
+ }
+
+ equip->id = dbi_result_get_ulonglong(result, "id");
+
+ string = dbi_result_get_string(result, "imei");
+ if (string)
+ osmo_strlcpy(equip->imei, string, sizeof(equip->imei));
+
+ string = dbi_result_get_string(result, "classmark1");
+ if (string) {
+ cm1 = atoi(string) & 0xff;
+ memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1));
+ }
+
+ equip->classmark2_len = dbi_result_get_field_length(result, "classmark2");
+ cm2 = dbi_result_get_binary(result, "classmark2");
+ if (equip->classmark2_len > sizeof(equip->classmark2))
+ equip->classmark2_len = sizeof(equip->classmark2);
+ if (cm2)
+ memcpy(equip->classmark2, cm2, equip->classmark2_len);
+
+ equip->classmark3_len = dbi_result_get_field_length(result, "classmark3");
+ cm3 = dbi_result_get_binary(result, "classmark3");
+ if (equip->classmark3_len > sizeof(equip->classmark3))
+ equip->classmark3_len = sizeof(equip->classmark3);
+ if (cm3)
+ memcpy(equip->classmark3, cm3, equip->classmark3_len);
+
+ dbi_result_free(result);
+
+ return 0;
+}
+
+int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+ struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+ const unsigned char *a3a8_ki;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT * FROM AuthKeys WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result)
+ return -EIO;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return -ENOENT;
+ }
+
+ ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id");
+ ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki");
+ a3a8_ki = dbi_result_get_binary(result, "a3a8_ki");
+ if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki))
+ ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki);
+ memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len);
+
+ dbi_result_free(result);
+
+ return 0;
+}
+
+int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+ struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+ struct gsm_auth_info ainfo_old;
+ int rc, upd;
+ unsigned char *ki_str;
+
+ /* Deletion ? */
+ if (ainfo == NULL) {
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM AuthKeys WHERE subscriber_id=%llu",
+ subscr->id);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+
+ return 0;
+ }
+
+ /* Check if already existing */
+ rc = db_get_authinfo_for_subscr(&ainfo_old, subscr);
+ if (rc && rc != -ENOENT)
+ return rc;
+ upd = rc ? 0 : 1;
+
+ /* Update / Insert */
+ dbi_conn_quote_binary_copy(conn,
+ ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str);
+
+ if (!upd) {
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO AuthKeys "
+ "(subscriber_id, algorithm_id, a3a8_ki) "
+ "VALUES (%llu, %u, %s)",
+ subscr->id, ainfo->auth_algo, ki_str);
+ } else {
+ result = dbi_conn_queryf(conn,
+ "UPDATE AuthKeys "
+ "SET algorithm_id=%u, a3a8_ki=%s "
+ "WHERE subscriber_id=%llu",
+ ainfo->auth_algo, ki_str, subscr->id);
+ }
+
+ free(ki_str);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+
+ return 0;
+}
+
+int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+ struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+ int len;
+ const unsigned char *blob;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT * FROM AuthLastTuples WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result)
+ return -EIO;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return -ENOENT;
+ }
+
+ memset(atuple, 0, sizeof(*atuple));
+
+ atuple->use_count = dbi_result_get_ulonglong(result, "use_count");
+ atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq");
+
+ len = dbi_result_get_field_length(result, "rand");
+ if (len != sizeof(atuple->vec.rand))
+ goto err_size;
+
+ blob = dbi_result_get_binary(result, "rand");
+ memcpy(atuple->vec.rand, blob, len);
+
+ len = dbi_result_get_field_length(result, "sres");
+ if (len != sizeof(atuple->vec.sres))
+ goto err_size;
+
+ blob = dbi_result_get_binary(result, "sres");
+ memcpy(atuple->vec.sres, blob, len);
+
+ len = dbi_result_get_field_length(result, "kc");
+ if (len != sizeof(atuple->vec.kc))
+ goto err_size;
+
+ blob = dbi_result_get_binary(result, "kc");
+ memcpy(atuple->vec.kc, blob, len);
+
+ dbi_result_free(result);
+
+ return 0;
+
+err_size:
+ dbi_result_free(result);
+ return -EIO;
+}
+
+int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+ struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+ int rc, upd;
+ struct gsm_auth_tuple atuple_old;
+ unsigned char *rand_str, *sres_str, *kc_str;
+
+ /* Deletion ? */
+ if (atuple == NULL) {
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
+ subscr->id);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+
+ return 0;
+ }
+
+ /* Check if already existing */
+ rc = db_get_lastauthtuple_for_subscr(&atuple_old, subscr);
+ if (rc && rc != -ENOENT)
+ return rc;
+ upd = rc ? 0 : 1;
+
+ /* Update / Insert */
+ dbi_conn_quote_binary_copy(conn,
+ atuple->vec.rand, sizeof(atuple->vec.rand), &rand_str);
+ dbi_conn_quote_binary_copy(conn,
+ atuple->vec.sres, sizeof(atuple->vec.sres), &sres_str);
+ dbi_conn_quote_binary_copy(conn,
+ atuple->vec.kc, sizeof(atuple->vec.kc), &kc_str);
+
+ if (!upd) {
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO AuthLastTuples "
+ "(subscriber_id, issued, use_count, "
+ "key_seq, rand, sres, kc) "
+ "VALUES (%llu, datetime('now'), %u, "
+ "%u, %s, %s, %s ) ",
+ subscr->id, atuple->use_count, atuple->key_seq,
+ rand_str, sres_str, kc_str);
+ } else {
+ char *issued = atuple->key_seq == atuple_old.key_seq ?
+ "issued" : "datetime('now')";
+ result = dbi_conn_queryf(conn,
+ "UPDATE AuthLastTuples "
+ "SET issued=%s, use_count=%u, "
+ "key_seq=%u, rand=%s, sres=%s, kc=%s "
+ "WHERE subscriber_id = %llu",
+ issued, atuple->use_count, atuple->key_seq,
+ rand_str, sres_str, kc_str, subscr->id);
+ }
+
+ free(rand_str);
+ free(sres_str);
+ free(kc_str);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+
+ return 0;
+}
+
+static void db_set_from_query(struct gsm_subscriber *subscr, dbi_conn result)
+{
+ const char *string;
+ string = dbi_result_get_string(result, "imsi");
+ if (string)
+ osmo_strlcpy(subscr->imsi, string, sizeof(subscr->imsi));
+
+ string = dbi_result_get_string(result, "tmsi");
+ if (string)
+ subscr->tmsi = tmsi_from_string(string);
+
+ string = dbi_result_get_string(result, "name");
+ if (string)
+ osmo_strlcpy(subscr->name, string, sizeof(subscr->name));
+
+ string = dbi_result_get_string(result, "extension");
+ if (string)
+ osmo_strlcpy(subscr->extension, string, sizeof(subscr->extension));
+
+ subscr->lac = dbi_result_get_ulonglong(result, "lac");
+
+ if (!dbi_result_field_is_null(result, "expire_lu"))
+ subscr->expire_lu = dbi_result_get_datetime(result, "expire_lu");
+ else
+ subscr->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION;
+
+ subscr->authorized = dbi_result_get_ulonglong(result, "authorized");
+
+}
+
+#define BASE_QUERY "SELECT * FROM Subscriber "
+struct gsm_subscriber *db_get_subscriber(enum gsm_subscriber_field field,
+ const char *id)
+{
+ dbi_result result;
+ char *quoted;
+ struct gsm_subscriber *subscr;
+
+ switch (field) {
+ case GSM_SUBSCRIBER_IMSI:
+ dbi_conn_quote_string_copy(conn, id, &quoted);
+ result = dbi_conn_queryf(conn,
+ BASE_QUERY
+ "WHERE imsi = %s ",
+ quoted
+ );
+ free(quoted);
+ break;
+ case GSM_SUBSCRIBER_TMSI:
+ dbi_conn_quote_string_copy(conn, id, &quoted);
+ result = dbi_conn_queryf(conn,
+ BASE_QUERY
+ "WHERE tmsi = %s ",
+ quoted
+ );
+ free(quoted);
+ break;
+ case GSM_SUBSCRIBER_EXTENSION:
+ dbi_conn_quote_string_copy(conn, id, &quoted);
+ result = dbi_conn_queryf(conn,
+ BASE_QUERY
+ "WHERE extension = %s ",
+ quoted
+ );
+ free(quoted);
+ break;
+ case GSM_SUBSCRIBER_ID:
+ dbi_conn_quote_string_copy(conn, id, &quoted);
+ result = dbi_conn_queryf(conn,
+ BASE_QUERY
+ "WHERE id = %s ", quoted);
+ free(quoted);
+ break;
+ default:
+ LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n");
+ return NULL;
+ }
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n");
+ return NULL;
+ }
+ if (!dbi_result_next_row(result)) {
+ DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n",
+ field, id);
+ dbi_result_free(result);
+ return NULL;
+ }
+
+ subscr = subscr_alloc();
+ subscr->id = dbi_result_get_ulonglong(result, "id");
+
+ db_set_from_query(subscr, result);
+ DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %x, EXTEN '%s', LAC %hu, AUTH %u\n",
+ subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension,
+ subscr->lac, subscr->authorized);
+ dbi_result_free(result);
+
+ get_equipment_by_subscr(subscr);
+
+ return subscr;
+}
+
+int db_subscriber_update(struct gsm_subscriber *subscr)
+{
+ char buf[32];
+ dbi_result result;
+
+ /* Copy the id to a string as queryf with %llu is failing */
+ sprintf(buf, "%llu", subscr->id);
+ result = dbi_conn_queryf(conn,
+ BASE_QUERY
+ "WHERE id = %s", buf);
+
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber: %llu\n", subscr->id);
+ return -EIO;
+ }
+ if (!dbi_result_next_row(result)) {
+ DEBUGP(DDB, "Failed to find the Subscriber. %llu\n",
+ subscr->id);
+ dbi_result_free(result);
+ return -EIO;
+ }
+
+ db_set_from_query(subscr, result);
+ dbi_result_free(result);
+ get_equipment_by_subscr(subscr);
+
+ return 0;
+}
+
+int db_sync_subscriber(struct gsm_subscriber *subscriber)
+{
+ dbi_result result;
+ char tmsi[14];
+ char *q_tmsi, *q_name, *q_extension;
+
+ dbi_conn_quote_string_copy(conn,
+ subscriber->name, &q_name);
+ if (subscriber->extension[0] != '\0')
+ dbi_conn_quote_string_copy(conn,
+ subscriber->extension, &q_extension);
+ else
+ q_extension = strdup("NULL");
+
+ if (subscriber->tmsi != GSM_RESERVED_TMSI) {
+ sprintf(tmsi, "%u", subscriber->tmsi);
+ dbi_conn_quote_string_copy(conn,
+ tmsi,
+ &q_tmsi);
+ } else
+ q_tmsi = strdup("NULL");
+
+ if (subscriber->expire_lu == GSM_SUBSCRIBER_NO_EXPIRATION) {
+ result = dbi_conn_queryf(conn,
+ "UPDATE Subscriber "
+ "SET updated = datetime('now'), "
+ "name = %s, "
+ "extension = %s, "
+ "authorized = %i, "
+ "tmsi = %s, "
+ "lac = %i, "
+ "expire_lu = NULL "
+ "WHERE imsi = %s ",
+ q_name,
+ q_extension,
+ subscriber->authorized,
+ q_tmsi,
+ subscriber->lac,
+ subscriber->imsi);
+ } else {
+ result = dbi_conn_queryf(conn,
+ "UPDATE Subscriber "
+ "SET updated = datetime('now'), "
+ "name = %s, "
+ "extension = %s, "
+ "authorized = %i, "
+ "tmsi = %s, "
+ "lac = %i, "
+ "expire_lu = datetime(%i, 'unixepoch') "
+ "WHERE imsi = %s ",
+ q_name,
+ q_extension,
+ subscriber->authorized,
+ q_tmsi,
+ subscriber->lac,
+ (int) subscriber->expire_lu,
+ subscriber->imsi);
+ }
+
+ free(q_tmsi);
+ free(q_name);
+ free(q_extension);
+
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n");
+ return 1;
+ }
+
+ dbi_result_free(result);
+
+ return 0;
+}
+
+int db_subscriber_delete(struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM AuthKeys WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete Authkeys for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete AuthLastTuples for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM AuthToken WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete AuthToken for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM EquipmentWatch WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete EquipmentWatch for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ if (subscr->extension[0] != '\0') {
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
+ subscr->extension, subscr->extension);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete SMS for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+ }
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM VLR WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete VLR for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM ApduBlobs WHERE subscriber_id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete ApduBlobs for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ result = dbi_conn_queryf(conn,
+ "DELETE FROM Subscriber WHERE id=%llu",
+ subscr->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR,
+ "Failed to delete Subscriber for %llu\n", subscr->id);
+ return -1;
+ }
+ dbi_result_free(result);
+
+ return 0;
+}
+
+/**
+ * List all the authorized and non-expired subscribers. The callback will
+ * be called one by one. The subscr argument is not fully initialize and
+ * subscr_get/subscr_put must not be called. The passed in pointer will be
+ * deleted after the callback by the database call.
+ */
+int db_subscriber_list_active(void (*cb)(struct gsm_subscriber*,void*), void *closure)
+{
+ dbi_result result;
+
+ result = dbi_conn_query(conn,
+ "SELECT * from Subscriber WHERE LAC != 0 AND authorized = 1");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to list active subscribers\n");
+ return -1;
+ }
+
+ while (dbi_result_next_row(result)) {
+ struct gsm_subscriber *subscr;
+
+ subscr = subscr_alloc();
+ subscr->id = dbi_result_get_ulonglong(result, "id");
+ db_set_from_query(subscr, result);
+ cb(subscr, closure);
+ OSMO_ASSERT(subscr->use_count == 1);
+ llist_del(&subscr->entry);
+ talloc_free(subscr);
+ }
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_sync_equipment(struct gsm_equipment *equip)
+{
+ dbi_result result;
+ unsigned char *cm2, *cm3;
+ char *q_imei;
+ uint8_t classmark1;
+
+ memcpy(&classmark1, &equip->classmark1, sizeof(classmark1));
+ DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x",
+ equip->imei, classmark1);
+ if (equip->classmark2_len)
+ DEBUGPC(DDB, ", classmark2=%s",
+ osmo_hexdump(equip->classmark2, equip->classmark2_len));
+ if (equip->classmark3_len)
+ DEBUGPC(DDB, ", classmark3=%s",
+ osmo_hexdump(equip->classmark3, equip->classmark3_len));
+ DEBUGPC(DDB, "\n");
+
+ dbi_conn_quote_binary_copy(conn, equip->classmark2,
+ equip->classmark2_len, &cm2);
+ dbi_conn_quote_binary_copy(conn, equip->classmark3,
+ equip->classmark3_len, &cm3);
+ dbi_conn_quote_string_copy(conn, equip->imei, &q_imei);
+
+ result = dbi_conn_queryf(conn,
+ "UPDATE Equipment SET "
+ "updated = datetime('now'), "
+ "classmark1 = %u, "
+ "classmark2 = %s, "
+ "classmark3 = %s "
+ "WHERE imei = %s ",
+ classmark1, cm2, cm3, q_imei);
+
+ free(cm2);
+ free(cm3);
+ free(q_imei);
+
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n");
+ return -EIO;
+ }
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_subscriber_expire(void *priv, void (*callback)(void *priv, long long unsigned int id))
+{
+ dbi_result result;
+
+ result = dbi_conn_query(conn,
+ "SELECT id "
+ "FROM Subscriber "
+ "WHERE lac != 0 AND "
+ "( expire_lu is NOT NULL "
+ "AND expire_lu < datetime('now') ) "
+ "LIMIT 1");
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to get expired subscribers\n");
+ return -EIO;
+ }
+
+ while (dbi_result_next_row(result))
+ callback(priv, dbi_result_get_ulonglong(result, "id"));
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber)
+{
+ dbi_result result = NULL;
+ char tmsi[14];
+ char *tmsi_quoted;
+
+ for (;;) {
+ if (RAND_bytes((uint8_t *) &subscriber->tmsi, sizeof(subscriber->tmsi)) != 1) {
+ LOGP(DDB, LOGL_ERROR, "RAND_bytes failed\n");
+ return 1;
+ }
+ if (subscriber->tmsi == GSM_RESERVED_TMSI)
+ continue;
+
+ sprintf(tmsi, "%u", subscriber->tmsi);
+ dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted);
+ result = dbi_conn_queryf(conn,
+ "SELECT * FROM Subscriber "
+ "WHERE tmsi = %s ",
+ tmsi_quoted);
+
+ free(tmsi_quoted);
+
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+ "while allocating new TMSI.\n");
+ return 1;
+ }
+ if (dbi_result_get_numrows(result)) {
+ dbi_result_free(result);
+ continue;
+ }
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n",
+ subscriber->tmsi, subscriber->imsi);
+ return db_sync_subscriber(subscriber);
+ }
+ dbi_result_free(result);
+ }
+ return 0;
+}
+
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber, uint64_t smin,
+ uint64_t smax)
+{
+ dbi_result result = NULL;
+ uint64_t try;
+
+ for (;;) {
+ try = (rand() % (smax - smin + 1) + smin);
+ result = dbi_conn_queryf(conn,
+ "SELECT * FROM Subscriber "
+ "WHERE extension = %"PRIu64,
+ try
+ );
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+ "while allocating new extension.\n");
+ return 1;
+ }
+ if (dbi_result_get_numrows(result)){
+ dbi_result_free(result);
+ continue;
+ }
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ break;
+ }
+ dbi_result_free(result);
+ }
+ sprintf(subscriber->extension, "%"PRIu64, try);
+ DEBUGP(DDB, "Allocated extension %"PRIu64 " for IMSI %s.\n", try, subscriber->imsi);
+ return db_sync_subscriber(subscriber);
+}
+/*
+ * try to allocate a new unique token for this subscriber and return it
+ * via a parameter. if the subscriber already has a token, return
+ * an error.
+ */
+
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, uint32_t *token)
+{
+ dbi_result result;
+ uint32_t try;
+
+ for (;;) {
+ if (RAND_bytes((uint8_t *) &try, sizeof(try)) != 1) {
+ LOGP(DDB, LOGL_ERROR, "RAND_bytes failed\n");
+ return 1;
+ }
+ if (!try) /* 0 is an invalid token */
+ continue;
+ result = dbi_conn_queryf(conn,
+ "SELECT * FROM AuthToken "
+ "WHERE subscriber_id = %llu OR token = \"%08X\" ",
+ subscriber->id, try);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken "
+ "while allocating new token.\n");
+ return 1;
+ }
+ if (dbi_result_get_numrows(result)) {
+ dbi_result_free(result);
+ continue;
+ }
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ break;
+ }
+ dbi_result_free(result);
+ }
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO AuthToken "
+ "(subscriber_id, created, token) "
+ "VALUES "
+ "(%llu, datetime('now'), \"%08X\") ",
+ subscriber->id, try);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for "
+ "IMSI %s.\n", try, subscriber->imsi);
+ return 1;
+ }
+ dbi_result_free(result);
+ *token = try;
+ DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi);
+
+ return 0;
+}
+
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM23003_IMEISV_NUM_DIGITS])
+{
+ unsigned long long equipment_id, watch_id;
+ dbi_result result;
+
+ osmo_strlcpy(subscriber->equipment.imei, imei, sizeof(subscriber->equipment.imei));
+
+ result = dbi_conn_queryf(conn,
+ "INSERT OR IGNORE INTO Equipment "
+ "(imei, created, updated) "
+ "VALUES "
+ "(%s, datetime('now'), datetime('now')) ",
+ imei);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n");
+ return 1;
+ }
+
+ equipment_id = 0;
+ if (dbi_result_get_numrows_affected(result)) {
+ equipment_id = dbi_conn_sequence_last(conn, NULL);
+ }
+ dbi_result_free(result);
+
+ if (equipment_id)
+ DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei);
+ else {
+ result = dbi_conn_queryf(conn,
+ "SELECT id FROM Equipment "
+ "WHERE imei = %s ",
+ imei
+ );
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n");
+ return 1;
+ }
+ if (!dbi_result_next_row(result)) {
+ LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n");
+ dbi_result_free(result);
+ return 1;
+ }
+ equipment_id = dbi_result_get_ulonglong(result, "id");
+ dbi_result_free(result);
+ }
+
+ result = dbi_conn_queryf(conn,
+ "INSERT OR IGNORE INTO EquipmentWatch "
+ "(subscriber_id, equipment_id, created, updated) "
+ "VALUES "
+ "(%llu, %llu, datetime('now'), datetime('now')) ",
+ subscriber->id, equipment_id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n");
+ return 1;
+ }
+
+ watch_id = 0;
+ if (dbi_result_get_numrows_affected(result))
+ watch_id = dbi_conn_sequence_last(conn, NULL);
+
+ dbi_result_free(result);
+ if (watch_id)
+ DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+ equipment_id, subscriber->imsi, imei);
+ else {
+ result = dbi_conn_queryf(conn,
+ "UPDATE EquipmentWatch "
+ "SET updated = datetime('now') "
+ "WHERE subscriber_id = %llu AND equipment_id = %llu ",
+ subscriber->id, equipment_id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n");
+ return 1;
+ }
+ dbi_result_free(result);
+ DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+ equipment_id, subscriber->imsi, imei);
+ }
+
+ return 0;
+}
+
+/* store an [unsent] SMS to the database */
+int db_sms_store(struct gsm_sms *sms)
+{
+ dbi_result result;
+ char *q_text, *q_daddr, *q_saddr;
+ unsigned char *q_udata;
+ char *validity_timestamp = "2222-2-2";
+
+ /* FIXME: generate validity timestamp based on validity_minutes */
+
+ dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
+ dbi_conn_quote_string_copy(conn, (char *)sms->dst.addr, &q_daddr);
+ dbi_conn_quote_string_copy(conn, (char *)sms->src.addr, &q_saddr);
+ dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
+ &q_udata);
+
+ /* FIXME: correct validity period */
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO SMS "
+ "(created, valid_until, "
+ "reply_path_req, status_rep_req, protocol_id, "
+ "data_coding_scheme, ud_hdr_ind, "
+ "user_data, text, "
+ "dest_addr, dest_ton, dest_npi, "
+ "src_addr, src_ton, src_npi) VALUES "
+ "(datetime('now'), %u, "
+ "%u, %u, %u, "
+ "%u, %u, "
+ "%s, %s, "
+ "%s, %u, %u, "
+ "%s, %u, %u)",
+ validity_timestamp,
+ sms->reply_path_req, sms->status_rep_req, sms->protocol_id,
+ sms->data_coding_scheme, sms->ud_hdr_ind,
+ q_udata, q_text,
+ q_daddr, sms->dst.ton, sms->dst.npi,
+ q_saddr, sms->src.ton, sms->src.npi);
+ free(q_text);
+ free(q_udata);
+ free(q_daddr);
+ free(q_saddr);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+ return 0;
+}
+
+static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result)
+{
+ struct gsm_sms *sms = sms_alloc();
+ const char *text, *daddr, *saddr;
+ const unsigned char *user_data;
+
+ if (!sms)
+ return NULL;
+
+ sms->id = dbi_result_get_ulonglong(result, "id");
+
+ /* FIXME: validity */
+ /* FIXME: those should all be get_uchar, but sqlite3 is braindead */
+ sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
+ sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
+ sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
+ sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
+ sms->data_coding_scheme = dbi_result_get_ulonglong(result,
+ "data_coding_scheme");
+ /* sms->msg_ref is temporary and not stored in DB */
+
+ sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi");
+ sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton");
+ daddr = dbi_result_get_string(result, "dest_addr");
+ if (daddr)
+ osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
+ sms->receiver = subscr_get_by_extension(net->subscr_group, sms->dst.addr);
+
+ sms->src.npi = dbi_result_get_ulonglong(result, "src_npi");
+ sms->src.ton = dbi_result_get_ulonglong(result, "src_ton");
+ saddr = dbi_result_get_string(result, "src_addr");
+ if (saddr)
+ osmo_strlcpy(sms->src.addr, saddr, sizeof(sms->src.addr));
+
+ sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+ user_data = dbi_result_get_binary(result, "user_data");
+ if (sms->user_data_len > sizeof(sms->user_data))
+ sms->user_data_len = (uint8_t) sizeof(sms->user_data);
+ memcpy(sms->user_data, user_data, sms->user_data_len);
+
+ text = dbi_result_get_string(result, "text");
+ if (text)
+ osmo_strlcpy(sms->text, text, sizeof(sms->text));
+ return sms;
+}
+
+struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id)
+{
+ dbi_result result;
+ struct gsm_sms *sms;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT * FROM SMS WHERE SMS.id = %llu", id);
+ if (!result)
+ return NULL;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return NULL;
+ }
+
+ sms = sms_from_result(net, result);
+
+ dbi_result_free(result);
+
+ return sms;
+}
+
+/* retrieve the next unsent SMS with ID >= min_id */
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id)
+{
+ dbi_result result;
+ struct gsm_sms *sms;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT SMS.* "
+ "FROM SMS JOIN Subscriber ON "
+ "SMS.dest_addr = Subscriber.extension "
+ "WHERE SMS.id >= %llu AND SMS.sent IS NULL "
+ "AND Subscriber.lac > 0 "
+ "ORDER BY SMS.id LIMIT 1",
+ min_id);
+ if (!result)
+ return NULL;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return NULL;
+ }
+
+ sms = sms_from_result(net, result);
+
+ dbi_result_free(result);
+
+ return sms;
+}
+
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net,
+ unsigned long long min_subscr_id,
+ unsigned int failed)
+{
+ dbi_result result;
+ struct gsm_sms *sms;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT SMS.* "
+ "FROM SMS JOIN Subscriber ON "
+ "SMS.dest_addr = Subscriber.extension "
+ "WHERE Subscriber.id >= %llu AND SMS.sent IS NULL "
+ "AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u "
+ "ORDER BY Subscriber.id, SMS.id LIMIT 1",
+ min_subscr_id, failed);
+ if (!result)
+ return NULL;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return NULL;
+ }
+
+ sms = sms_from_result(net, result);
+
+ dbi_result_free(result);
+
+ return sms;
+}
+
+/* retrieve the next unsent SMS for a given subscriber */
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr)
+{
+ dbi_result result;
+ struct gsm_sms *sms;
+
+ result = dbi_conn_queryf(conn,
+ "SELECT SMS.* "
+ "FROM SMS JOIN Subscriber ON "
+ "SMS.dest_addr = Subscriber.extension "
+ "WHERE Subscriber.id = %llu AND SMS.sent IS NULL "
+ "AND Subscriber.lac > 0 "
+ "ORDER BY SMS.id LIMIT 1",
+ subscr->id);
+ if (!result)
+ return NULL;
+
+ if (!dbi_result_next_row(result)) {
+ dbi_result_free(result);
+ return NULL;
+ }
+
+ sms = sms_from_result(subscr->group->net, result);
+
+ dbi_result_free(result);
+
+ return sms;
+}
+
+/* mark a given SMS as delivered */
+int db_sms_mark_delivered(struct gsm_sms *sms)
+{
+ dbi_result result;
+
+ result = dbi_conn_queryf(conn,
+ "UPDATE SMS "
+ "SET sent = datetime('now') "
+ "WHERE id = %llu", sms->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id);
+ return 1;
+ }
+
+ dbi_result_free(result);
+ return 0;
+}
+
+/* increase the number of attempted deliveries */
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms)
+{
+ dbi_result result;
+
+ result = dbi_conn_queryf(conn,
+ "UPDATE SMS "
+ "SET deliver_attempts = deliver_attempts + 1 "
+ "WHERE id = %llu", sms->id);
+ if (!result) {
+ LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for "
+ "SMS %llu.\n", sms->id);
+ return 1;
+ }
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_apdu_blob_store(struct gsm_subscriber *subscr,
+ uint8_t apdu_id_flags, uint8_t len,
+ uint8_t *apdu)
+{
+ dbi_result result;
+ unsigned char *q_apdu;
+
+ dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu);
+
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO ApduBlobs "
+ "(created,subscriber_id,apdu_id_flags,apdu) VALUES "
+ "(datetime('now'),%llu,%u,%s)",
+ subscr->id, apdu_id_flags, q_apdu);
+
+ free(q_apdu);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_store_counter(struct osmo_counter *ctr)
+{
+ dbi_result result;
+ char *q_name;
+
+ dbi_conn_quote_string_copy(conn, ctr->name, &q_name);
+
+ result = dbi_conn_queryf(conn,
+ "INSERT INTO Counters "
+ "(timestamp,name,value) VALUES "
+ "(datetime('now'),%s,%lu)", q_name, ctr->value);
+
+ free(q_name);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+ return 0;
+}
+
+static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num,
+ char *q_prefix)
+{
+ dbi_result result;
+ char *q_name;
+
+ dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name,
+ &q_name);
+
+ result = dbi_conn_queryf(conn,
+ "Insert INTO RateCounters "
+ "(timestamp,name,idx,value) VALUES "
+ "(datetime('now'),%s.%s,%u,%"PRIu64")",
+ q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current);
+
+ free(q_name);
+
+ if (!result)
+ return -EIO;
+
+ dbi_result_free(result);
+ return 0;
+}
+
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg)
+{
+ unsigned int i;
+ char *q_prefix;
+
+ dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix);
+
+ for (i = 0; i < ctrg->desc->num_ctr; i++)
+ db_store_rate_ctr(ctrg, i, q_prefix);
+
+ free(q_prefix);
+
+ return 0;
+}
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
new file mode 100644
index 000000000..89108e466
--- /dev/null
+++ b/src/libmsc/gsm_04_08.c
@@ -0,0 +1,4047 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <regex.h>
+#include <sys/types.h>
+
+#include "bscconfig.h"
+
+#include <openbsc/auth.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <osmocom/abis/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/transaction.h>
+#include <openbsc/ussd.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/bsc_api.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/handover.h>
+#include <openbsc/mncc_int.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/core/bitvec.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <assert.h>
+
+void *tall_locop_ctx;
+void *tall_authciphop_ctx;
+
+static int tch_rtp_signal(struct gsm_lchan *lchan, int signal);
+
+static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn);
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+ uint8_t pdisc, uint8_t msg_type);
+static void schedule_reject(struct gsm_subscriber_connection *conn);
+static void release_anchor(struct gsm_subscriber_connection *conn);
+
+struct gsm_lai {
+ uint16_t mcc;
+ uint16_t mnc;
+ uint16_t lac;
+};
+
+static int apply_codec_restrictions(struct gsm_bts *bts,
+ struct gsm_mncc_bearer_cap *bcap)
+{
+ int i, j;
+
+ /* remove unsupported speech versions from list */
+ for (i = 0, j = 0; bcap->speech_ver[i] >= 0; i++) {
+ if (bcap->speech_ver[i] == GSM48_BCAP_SV_FR)
+ bcap->speech_ver[j++] = GSM48_BCAP_SV_FR;
+ if (bcap->speech_ver[i] == GSM48_BCAP_SV_EFR && bts->codec.efr)
+ bcap->speech_ver[j++] = GSM48_BCAP_SV_EFR;
+ if (bcap->speech_ver[i] == GSM48_BCAP_SV_AMR_F && bts->codec.amr)
+ bcap->speech_ver[j++] = GSM48_BCAP_SV_AMR_F;
+ if (bcap->speech_ver[i] == GSM48_BCAP_SV_HR && bts->codec.hr)
+ bcap->speech_ver[j++] = GSM48_BCAP_SV_HR;
+ if (bcap->speech_ver[i] == GSM48_BCAP_SV_AMR_H && bts->codec.amr)
+ bcap->speech_ver[j++] = GSM48_BCAP_SV_AMR_H;
+ }
+ bcap->speech_ver[j] = -1;
+
+ return 0;
+}
+
+static uint32_t new_callref = 0x80000001;
+
+void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg)
+{
+ net->mncc_recv(net, msg);
+}
+
+static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection *conn,
+ struct gsm_trans *trans)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
+
+ /* if we get passed a transaction reference, do some common
+ * work that the caller no longer has to do */
+ if (trans) {
+ gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
+ msg->lchan = trans->conn->lchan;
+ }
+
+ if (msg->lchan) {
+ struct e1inp_sign_link *sign_link =
+ msg->lchan->ts->trx->rsl_link;
+
+ msg->dst = sign_link;
+ if (gsm48_hdr_pdisc(gh) == GSM48_PDISC_CC)
+ DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) "
+ "Sending '%s' to MS.\n",
+ sign_link->trx->bts->nr,
+ sign_link->trx->nr, msg->lchan->ts->nr,
+ gh->proto_discr & 0xf0,
+ gsm48_cc_msg_name(gh->msg_type));
+ else
+ DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) "
+ "Sending 0x%02x to MS.\n",
+ sign_link->trx->bts->nr,
+ sign_link->trx->nr, msg->lchan->ts->nr,
+ gh->proto_discr, gh->msg_type);
+ }
+
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message)
+{
+ struct gsm48_hdr *gh;
+ struct msgb *ss_notify;
+
+ ss_notify = gsm0480_create_notifySS(message);
+ if (!ss_notify)
+ return -1;
+
+ gsm0480_wrap_invoke(ss_notify, GSM0480_OP_CODE_NOTIFY_SS, 0);
+ uint8_t *data = msgb_push(ss_notify, 1);
+ data[0] = ss_notify->len - 1;
+ gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh));
+ gh->msg_type = GSM48_MT_CC_FACILITY;
+ return gsm48_conn_sendmsg(ss_notify, trans->conn, trans);
+}
+
+void release_security_operation(struct gsm_subscriber_connection *conn)
+{
+ if (!conn->sec_operation)
+ return;
+
+ talloc_free(conn->sec_operation);
+ conn->sec_operation = NULL;
+ msc_release_connection(conn);
+}
+
+void allocate_security_operation(struct gsm_subscriber_connection *conn)
+{
+ conn->sec_operation = talloc_zero(tall_authciphop_ctx,
+ struct gsm_security_operation);
+}
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+ gsm_cbfn *cb, void *cb_data)
+{
+ struct gsm_network *net = conn->network;
+ struct gsm_subscriber *subscr = conn->subscr;
+ struct gsm_security_operation *op;
+ struct gsm_auth_tuple atuple;
+ int status = -1, rc;
+
+ /* Check if we _can_ enable encryption. Cases where we can't:
+ * - Encryption disabled in config
+ * - Channel already secured (nothing to do)
+ * - Subscriber equipment doesn't support configured encryption
+ */
+ if (!net->a5_encryption) {
+ status = GSM_SECURITY_NOAVAIL;
+ } else if (conn->lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+ DEBUGP(DMM, "Requesting to secure an already secure channel");
+ status = GSM_SECURITY_ALREADY;
+ } else if (!ms_cm2_a5n_support(subscr->equipment.classmark2,
+ net->a5_encryption)) {
+ DEBUGP(DMM, "Subscriber equipment doesn't support requested encryption");
+ status = GSM_SECURITY_NOAVAIL;
+ }
+
+ /* If not done yet, try to get info for this user */
+ if (status < 0) {
+ rc = auth_get_tuple_for_subscr(&atuple, subscr, key_seq);
+ if (rc <= 0)
+ status = GSM_SECURITY_NOAVAIL;
+ }
+
+ /* Are we done yet ? */
+ if (status >= 0)
+ return cb ?
+ cb(GSM_HOOK_RR_SECURITY, status, NULL, conn, cb_data) :
+ 0;
+
+ /* Start an operation (can't have more than one pending !!!) */
+ if (conn->sec_operation)
+ return -EBUSY;
+
+ allocate_security_operation(conn);
+ op = conn->sec_operation;
+ op->cb = cb;
+ op->cb_data = cb_data;
+ memcpy(&op->atuple, &atuple, sizeof(struct gsm_auth_tuple));
+
+ /* FIXME: Should start a timer for completion ... */
+
+ /* Then do whatever is needed ... */
+ if (rc == AUTH_DO_AUTH_THEN_CIPH) {
+ /* Start authentication */
+ return gsm48_tx_mm_auth_req(conn, op->atuple.vec.rand, NULL,
+ op->atuple.key_seq);
+ } else if (rc == AUTH_DO_CIPH) {
+ /* Start ciphering directly */
+ return gsm0808_cipher_mode(conn, net->a5_encryption,
+ op->atuple.vec.kc, 8, 0);
+ }
+
+ return -EINVAL; /* not reached */
+}
+
+static bool subscr_regexp_check(const struct gsm_network *net, const char *imsi)
+{
+ if (!net->authorized_reg_str)
+ return false;
+
+ if (regexec(&net->authorized_regexp, imsi, 0, NULL, 0) != REG_NOMATCH)
+ return true;
+
+ return false;
+}
+
+static int authorize_subscriber(struct gsm_loc_updating_operation *loc,
+ struct gsm_subscriber *subscriber)
+{
+ if (!subscriber)
+ return 0;
+
+ /*
+ * Do not send accept yet as more information should arrive. Some
+ * phones will not send us the information and we will have to check
+ * what we want to do with that.
+ */
+ if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei))
+ return 0;
+
+ switch (subscriber->group->net->auth_policy) {
+ case GSM_AUTH_POLICY_CLOSED:
+ return subscriber->authorized;
+ case GSM_AUTH_POLICY_REGEXP:
+ if (subscriber->authorized)
+ return 1;
+ if (subscr_regexp_check(subscriber->group->net,
+ subscriber->imsi))
+ subscriber->authorized = 1;
+ return subscriber->authorized;
+ case GSM_AUTH_POLICY_TOKEN:
+ if (subscriber->authorized)
+ return subscriber->authorized;
+ return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT);
+ case GSM_AUTH_POLICY_ACCEPT_ALL:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void _release_loc_updating_req(struct gsm_subscriber_connection *conn, int release)
+{
+ if (!conn->loc_operation)
+ return;
+
+ /* No need to keep the connection up */
+ release_anchor(conn);
+
+ osmo_timer_del(&conn->loc_operation->updating_timer);
+ talloc_free(conn->loc_operation);
+ conn->loc_operation = NULL;
+ if (release)
+ msc_release_connection(conn);
+}
+
+static void loc_updating_failure(struct gsm_subscriber_connection *conn, int release)
+{
+ if (!conn->loc_operation)
+ return;
+ LOGP(DMM, LOGL_ERROR, "Location Updating failed for %s\n",
+ subscr_name(conn->subscr));
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED]);
+ _release_loc_updating_req(conn, release);
+}
+
+static void loc_updating_success(struct gsm_subscriber_connection *conn, int release)
+{
+ if (!conn->loc_operation)
+ return;
+ LOGP(DMM, LOGL_INFO, "Location Updating completed for %s\n",
+ subscr_name(conn->subscr));
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]);
+ _release_loc_updating_req(conn, release);
+}
+
+static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+ if (conn->loc_operation)
+ LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n");
+ loc_updating_failure(conn, 0);
+
+ conn->loc_operation = talloc_zero(tall_locop_ctx,
+ struct gsm_loc_updating_operation);
+}
+
+static int finish_lu(struct gsm_subscriber_connection *conn)
+{
+ int rc = 0;
+ int avoid_tmsi = conn->network->avoid_tmsi;
+
+ /* We're all good */
+ if (avoid_tmsi) {
+ conn->subscr->tmsi = GSM_RESERVED_TMSI;
+ db_sync_subscriber(conn->subscr);
+ } else {
+ db_subscriber_alloc_tmsi(conn->subscr);
+ }
+
+ rc = gsm0408_loc_upd_acc(conn);
+ if (conn->network->send_mm_info) {
+ /* send MM INFO with network name */
+ rc = gsm48_tx_mm_info(conn);
+ }
+
+ /* call subscr_update after putting the loc_upd_acc
+ * in the transmit queue, since S_SUBSCR_ATTACHED might
+ * trigger further action like SMS delivery */
+ subscr_update(conn->subscr, conn->bts,
+ GSM_SUBSCRIBER_UPDATE_ATTACHED);
+
+ /*
+ * The gsm0408_loc_upd_acc sends a MI with the TMSI. The
+ * MS needs to respond with a TMSI REALLOCATION COMPLETE
+ * (even if the TMSI is the same).
+ * If avoid_tmsi == true, we don't send a TMSI, we don't
+ * expect a reply and Location Updating is done.
+ */
+ if (avoid_tmsi)
+ loc_updating_success(conn, 1);
+
+ return rc;
+}
+
+static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *data, void *param)
+{
+ struct gsm_subscriber_connection *conn = data;
+ int rc = 0;
+
+ switch (event) {
+ case GSM_SECURITY_AUTH_FAILED:
+ loc_updating_failure(conn, 1);
+ break;
+
+ case GSM_SECURITY_ALREADY:
+ LOGP(DMM, LOGL_ERROR, "We don't expect LOCATION "
+ "UPDATING after CM SERVICE REQUEST\n");
+ /* fall through */
+
+ case GSM_SECURITY_NOAVAIL:
+ case GSM_SECURITY_SUCCEEDED:
+ rc = finish_lu(conn);
+ break;
+
+ default:
+ rc = -EINVAL;
+ };
+
+ return rc;
+}
+
+static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ if (!conn->loc_operation)
+ return 0;
+
+ if (authorize_subscriber(conn->loc_operation, conn->subscr))
+ return gsm48_secure_channel(conn,
+ conn->loc_operation->key_seq,
+ _gsm0408_authorize_sec_cb, NULL);
+ return 0;
+}
+
+void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+ struct gsm_trans *trans, *temp;
+
+ /* avoid someone issuing a clear */
+ conn->in_release = 1;
+
+ /*
+ * Cancel any outstanding location updating request
+ * operation taking place on the subscriber connection.
+ */
+ loc_updating_failure(conn, 0);
+
+ /* We might need to cancel the paging response or such. */
+ if (conn->sec_operation && conn->sec_operation->cb) {
+ conn->sec_operation->cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+ NULL, conn, conn->sec_operation->cb_data);
+ }
+
+ release_security_operation(conn);
+ release_anchor(conn);
+
+ /*
+ * Free all transactions that are associated with the released
+ * connection. The transaction code will inform the CC or SMS
+ * facilities that will send the release indications. As part of
+ * the CC REL_IND the remote leg might be released and this will
+ * trigger the call to trans_free. This is something the llist
+ * macro can not handle and we will need to re-iterate the list.
+ *
+ * TODO: Move the trans_list into the subscriber connection and
+ * create a pending list for MT transactions. These exist before
+ * we have a subscriber connection.
+ */
+restart:
+ llist_for_each_entry_safe(trans, temp, &conn->network->trans_list, entry) {
+ if (trans->conn == conn) {
+ trans_free(trans);
+ goto restart;
+ }
+ }
+}
+
+void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
+{
+ struct gsm_trans *trans, *temp;
+
+ LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n");
+
+ llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) {
+ if (trans->protocol == protocol) {
+ trans->callref = 0;
+ trans_free(trans);
+ }
+ }
+}
+
+/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
+int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause)
+{
+ struct gsm_bts *bts = conn->bts;
+ struct msgb *msg;
+
+ msg = gsm48_create_loc_upd_rej(cause);
+ if (!msg) {
+ LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+ return -1;
+ }
+
+ msg->lchan = conn->lchan;
+
+ LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT "
+ "LAC=%u BTS=%u\n", subscr_name(conn->subscr),
+ bts->location_area_code, bts->nr);
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
+static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD ACC");
+ struct gsm48_hdr *gh;
+ struct gsm48_loc_area_id *lai;
+ uint8_t *mid;
+
+ msg->lchan = conn->lchan;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_MM;
+ gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT;
+
+ lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
+ gsm48_generate_lai(lai, conn->network->country_code,
+ conn->network->network_code,
+ conn->bts->location_area_code);
+
+ if (conn->subscr->tmsi == GSM_RESERVED_TMSI) {
+ uint8_t mi[10];
+ int len;
+ len = gsm48_generate_mid_from_imsi(mi, conn->subscr->imsi);
+ mid = msgb_put(msg, len);
+ memcpy(mid, mi, len);
+ } else {
+ mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+ gsm48_generate_mid_from_tmsi(mid, conn->subscr->tmsi);
+ }
+
+ DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Transmit Chapter 9.2.10 Identity Request */
+static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, uint8_t id_type)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ID REQ");
+ struct gsm48_hdr *gh;
+
+ msg->lchan = conn->lchan;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+ gh->proto_discr = GSM48_PDISC_MM;
+ gh->msg_type = GSM48_MT_MM_ID_REQ;
+ gh->data[0] = id_type;
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static struct gsm_subscriber *subscr_create(const struct gsm_network *net,
+ const char *imsi)
+{
+ if (!net->auto_create_subscr)
+ return NULL;
+
+ if (!subscr_regexp_check(net, imsi))
+ return NULL;
+
+ return subscr_create_subscriber(net->subscr_group, imsi);
+}
+
+/* Parse Chapter 9.2.11 Identity Response */
+static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm_network *net = conn->network;
+ uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+ char mi_string[GSM48_MI_SIZE];
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+ DEBUGP(DMM, "IDENTITY RESPONSE: MI(%s)=%s\n",
+ gsm48_mi_type_name(mi_type), mi_string);
+
+ osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ /* look up subscriber based on IMSI, create if not found */
+ if (!conn->subscr) {
+ conn->subscr = subscr_get_by_imsi(net->subscr_group,
+ mi_string);
+ if (!conn->subscr)
+ conn->subscr = subscr_create(net, mi_string);
+ }
+ if (!conn->subscr && conn->loc_operation) {
+ gsm0408_loc_upd_rej(conn, net->reject_cause);
+ loc_updating_failure(conn, 1);
+ return 0;
+ }
+ if (conn->loc_operation)
+ conn->loc_operation->waiting_for_imsi = 0;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ case GSM_MI_TYPE_IMEISV:
+ /* update subscribe <-> IMEI mapping */
+ if (conn->subscr) {
+ db_subscriber_assoc_imei(conn->subscr, mi_string);
+ db_sync_equipment(&conn->subscr->equipment);
+ }
+ if (conn->loc_operation)
+ conn->loc_operation->waiting_for_imei = 0;
+ break;
+ }
+
+ /* Check if we can let the mobile station enter */
+ return gsm0408_authorize(conn, msg);
+}
+
+
+static void loc_upd_rej_cb(void *data)
+{
+ struct gsm_subscriber_connection *conn = data;
+
+ LOGP(DMM, LOGL_DEBUG, "Location Updating Request procedure timedout.\n");
+ gsm0408_loc_upd_rej(conn, conn->network->reject_cause);
+ loc_updating_failure(conn, 1);
+}
+
+static void schedule_reject(struct gsm_subscriber_connection *conn)
+{
+ osmo_timer_setup(&conn->loc_operation->updating_timer, loc_upd_rej_cb,
+ conn);
+ osmo_timer_schedule(&conn->loc_operation->updating_timer, 5, 0);
+}
+
+static const struct value_string lupd_names[] = {
+ { GSM48_LUPD_NORMAL, "NORMAL" },
+ { GSM48_LUPD_PERIODIC, "PERIODIC" },
+ { GSM48_LUPD_IMSI_ATT, "IMSI ATTACH" },
+ { 0, NULL }
+};
+
+/* Chapter 9.2.15: Receive Location Updating Request */
+static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_loc_upd_req *lu;
+ struct gsm_subscriber *subscr = NULL;
+ uint8_t mi_type;
+ char mi_string[GSM48_MI_SIZE];
+
+ lu = (struct gsm48_loc_upd_req *) gh->data;
+
+ mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+
+ DEBUGPC(DMM, "MI(%s)=%s type=%s ", gsm48_mi_type_name(mi_type),
+ mi_string, get_value_string(lupd_names, lu->type));
+
+ osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
+
+ switch (lu->type) {
+ case GSM48_LUPD_NORMAL:
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]);
+ break;
+ case GSM48_LUPD_IMSI_ATT:
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]);
+ break;
+ case GSM48_LUPD_PERIODIC:
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]);
+ break;
+ }
+
+ /*
+ * Pseudo Spoof detection: Just drop a second/concurrent
+ * location updating request.
+ */
+ if (conn->loc_operation) {
+ DEBUGPC(DMM, "ignoring request due an existing one: %p.\n",
+ conn->loc_operation);
+ gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR);
+ return 0;
+ }
+
+ allocate_loc_updating_req(conn);
+
+ conn->loc_operation->key_seq = lu->key_seq;
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ DEBUGPC(DMM, "\n");
+ /* we always want the IMEI, too */
+ mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+ conn->loc_operation->waiting_for_imei = 1;
+
+ /* look up subscriber based on IMSI, create if not found */
+ subscr = subscr_get_by_imsi(conn->network->subscr_group, mi_string);
+ if (!subscr)
+ subscr = subscr_create(conn->network, mi_string);
+ if (!subscr) {
+ gsm0408_loc_upd_rej(conn, conn->network->reject_cause);
+ loc_updating_failure(conn, 0); /* FIXME: set release == true? */
+ return 0;
+ }
+ break;
+ case GSM_MI_TYPE_TMSI:
+ DEBUGPC(DMM, "\n");
+ /* look up the subscriber based on TMSI, request IMSI if it fails */
+ subscr = subscr_get_by_tmsi(conn->network->subscr_group,
+ tmsi_from_string(mi_string));
+ if (!subscr) {
+ /* send IDENTITY REQUEST message to get IMSI */
+ mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI);
+ conn->loc_operation->waiting_for_imsi = 1;
+ }
+ /* we always want the IMEI, too */
+ mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+ conn->loc_operation->waiting_for_imei = 1;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ case GSM_MI_TYPE_IMEISV:
+ /* no sim card... FIXME: what to do ? */
+ DEBUGPC(DMM, "unimplemented mobile identity type\n");
+ break;
+ default:
+ DEBUGPC(DMM, "unknown mobile identity type\n");
+ break;
+ }
+
+ /* schedule the reject timer */
+ schedule_reject(conn);
+
+ if (!subscr) {
+ DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
+ /* FIXME: request id? close channel? */
+ return -EINVAL;
+ }
+
+ conn->subscr = subscr;
+ conn->subscr->equipment.classmark1 = lu->classmark1;
+
+ /* check if we can let the subscriber into our network immediately
+ * or if we need to wait for identity responses. */
+ return gsm0408_authorize(conn, msg);
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+static uint8_t bcdify(uint8_t value)
+{
+ uint8_t ret;
+
+ ret = value / 10;
+ ret |= (value % 10) << 4;
+
+ return ret;
+}
+
+
+/* Section 9.2.15a */
+int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 MM INF");
+ struct gsm48_hdr *gh;
+ struct gsm_network *net = conn->network;
+ uint8_t *ptr8;
+ int name_len, name_pad;
+
+ time_t cur_t;
+ struct tm* gmt_time;
+ struct tm* local_time;
+ int tzunits;
+ int dst = 0;
+
+ msg->lchan = conn->lchan;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_MM;
+ gh->msg_type = GSM48_MT_MM_INFO;
+
+ if (net->name_long) {
+#if 0
+ name_len = strlen(net->name_long);
+ /* 10.5.3.5a */
+ ptr8 = msgb_put(msg, 3);
+ ptr8[0] = GSM48_IE_NAME_LONG;
+ ptr8[1] = name_len*2 +1;
+ ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+ ptr16 = (uint16_t *) msgb_put(msg, name_len*2);
+ for (i = 0; i < name_len; i++)
+ ptr16[i] = htons(net->name_long[i]);
+
+ /* FIXME: Use Cell Broadcast, not UCS-2, since
+ * UCS-2 is only supported by later revisions of the spec */
+#endif
+ name_len = (strlen(net->name_long)*7)/8;
+ name_pad = (8 - strlen(net->name_long)*7)%8;
+ if (name_pad > 0)
+ name_len++;
+ /* 10.5.3.5a */
+ ptr8 = msgb_put(msg, 3);
+ ptr8[0] = GSM48_IE_NAME_LONG;
+ ptr8[1] = name_len +1;
+ ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+ ptr8 = msgb_put(msg, name_len);
+ gsm_7bit_encode_n(ptr8, name_len, net->name_long, NULL);
+
+ }
+
+ if (net->name_short) {
+#if 0
+ name_len = strlen(net->name_short);
+ /* 10.5.3.5a */
+ ptr8 = (uint8_t *) msgb_put(msg, 3);
+ ptr8[0] = GSM48_IE_NAME_SHORT;
+ ptr8[1] = name_len*2 + 1;
+ ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+ ptr16 = (uint16_t *) msgb_put(msg, name_len*2);
+ for (i = 0; i < name_len; i++)
+ ptr16[i] = htons(net->name_short[i]);
+#endif
+ name_len = (strlen(net->name_short)*7)/8;
+ name_pad = (8 - strlen(net->name_short)*7)%8;
+ if (name_pad > 0)
+ name_len++;
+ /* 10.5.3.5a */
+ ptr8 = (uint8_t *) msgb_put(msg, 3);
+ ptr8[0] = GSM48_IE_NAME_SHORT;
+ ptr8[1] = name_len +1;
+ ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+ ptr8 = msgb_put(msg, name_len);
+ gsm_7bit_encode_n(ptr8, name_len, net->name_short, NULL);
+
+ }
+
+ /* Section 10.5.3.9 */
+ cur_t = time(NULL);
+ gmt_time = gmtime(&cur_t);
+
+ ptr8 = msgb_put(msg, 8);
+ ptr8[0] = GSM48_IE_NET_TIME_TZ;
+ ptr8[1] = bcdify(gmt_time->tm_year % 100);
+ ptr8[2] = bcdify(gmt_time->tm_mon + 1);
+ ptr8[3] = bcdify(gmt_time->tm_mday);
+ ptr8[4] = bcdify(gmt_time->tm_hour);
+ ptr8[5] = bcdify(gmt_time->tm_min);
+ ptr8[6] = bcdify(gmt_time->tm_sec);
+
+ if (net->tz.override) {
+ /* Convert tz.hr and tz.mn to units */
+ if (net->tz.hr < 0) {
+ tzunits = ((net->tz.hr/-1)*4);
+ tzunits = tzunits + (net->tz.mn/15);
+ ptr8[7] = bcdify(tzunits);
+ /* Set negative time */
+ ptr8[7] |= 0x08;
+ }
+ else {
+ tzunits = net->tz.hr*4;
+ tzunits = tzunits + (net->tz.mn/15);
+ ptr8[7] = bcdify(tzunits);
+ }
+ /* Convert DST value */
+ if (net->tz.dst >= 0 && net->tz.dst <= 2)
+ dst = net->tz.dst;
+ }
+ else {
+ /* Need to get GSM offset and convert into 15 min units */
+ /* This probably breaks if gmtoff returns a value not evenly divisible by 15? */
+ local_time = localtime(&cur_t);
+#ifdef HAVE_TM_GMTOFF_IN_TM
+ tzunits = (local_time->tm_gmtoff/60)/15;
+#else
+#warning find a portable way to obtain the timezone offset
+ tzunits = 0;
+#endif
+ if (tzunits < 0) {
+ tzunits = tzunits/-1;
+ ptr8[7] = bcdify(tzunits);
+ /* Flip it to negative */
+ ptr8[7] |= 0x08;
+ }
+ else
+ ptr8[7] = bcdify(tzunits);
+
+ /* Does not support DST +2 */
+ if (local_time->tm_isdst)
+ dst = 1;
+ }
+
+ if (dst) {
+ ptr8 = msgb_put(msg, 3);
+ ptr8[0] = GSM48_IE_NET_DST;
+ ptr8[1] = 1;
+ ptr8[2] = dst;
+ }
+
+ DEBUGP(DMM, "-> MM INFO\n");
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/*! Send an Authentication Request to MS on the given subscriber connection
+ * according to 3GPP/ETSI TS 24.008, Section 9.2.2.
+ * \param[in] conn Subscriber connection to send on.
+ * \param[in] rand Random challenge token to send, must be 16 bytes long.
+ * \param[in] autn r99: In case of UMTS mutual authentication, AUTN token to
+ * send; must be 16 bytes long, or pass NULL for plain GSM auth.
+ * \param[in] key_seq auth tuple's sequence number.
+ */
+int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, uint8_t *rand,
+ uint8_t *autn, int key_seq)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH REQ");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar));
+
+ DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", osmo_hexdump(rand, 16));
+ if (autn)
+ DEBUGP(DMM, " AUTH REQ (autn = %s)\n", osmo_hexdump(autn, 16));
+
+ msg->lchan = conn->lchan;
+ gh->proto_discr = GSM48_PDISC_MM;
+ gh->msg_type = GSM48_MT_MM_AUTH_REQ;
+
+ ar->key_seq = key_seq;
+
+ /* 16 bytes RAND parameters */
+ osmo_static_assert(sizeof(ar->rand) == 16, sizeof_auth_req_r99_rand);
+ if (rand)
+ memcpy(ar->rand, rand, 16);
+
+
+ /* 16 bytes AUTN */
+ if (autn)
+ msgb_tlv_put(msg, GSM48_IE_AUTN, 16, autn);
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Section 9.2.1 */
+int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn)
+{
+ DEBUGP(DMM, "-> AUTH REJECT\n");
+ return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
+}
+
+/*
+ * At the 30C3 phones miss their periodic update
+ * interval a lot and then remain unreachable. In case
+ * we still know the TMSI we can just attach it again.
+ */
+static void implit_attach(struct gsm_subscriber_connection *conn)
+{
+ if (conn->subscr->lac != GSM_LAC_RESERVED_DETACHED)
+ return;
+
+ subscr_update(conn->subscr, conn->bts,
+ GSM_SUBSCRIBER_UPDATE_ATTACHED);
+}
+
+
+static int _gsm48_rx_mm_serv_req_sec_cb(
+ unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *data, void *param)
+{
+ struct gsm_subscriber_connection *conn = data;
+ int rc = 0;
+
+ /* auth failed or succeeded, the timer was stopped */
+ conn->expire_timer_stopped = 1;
+
+ switch (event) {
+ case GSM_SECURITY_AUTH_FAILED:
+ /* Nothing to do */
+ break;
+
+ case GSM_SECURITY_NOAVAIL:
+ case GSM_SECURITY_ALREADY:
+ rc = gsm48_tx_mm_serv_ack(conn);
+ implit_attach(conn);
+ break;
+
+ case GSM_SECURITY_SUCCEEDED:
+ /* nothing to do. CIPHER MODE COMMAND is
+ * implicit CM SERV ACK */
+ implit_attach(conn);
+ break;
+
+ default:
+ rc = -EINVAL;
+ };
+
+ return rc;
+}
+
+/*
+ * Handle CM Service Requests
+ * a) Verify that the packet is long enough to contain the information
+ * we require otherwsie reject with INCORRECT_MESSAGE
+ * b) Try to parse the TMSI. If we do not have one reject
+ * c) Check that we know the subscriber with the TMSI otherwise reject
+ * with a HLR cause
+ * d) Set the subscriber on the gsm_lchan and accept
+ */
+static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ uint8_t mi_type;
+ char mi_string[GSM48_MI_SIZE];
+
+ struct gsm_network *network = conn->network;
+ struct gsm_subscriber *subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_service_request *req =
+ (struct gsm48_service_request *)gh->data;
+ /* unfortunately in Phase1 the classmark2 length is variable */
+ uint8_t classmark2_len = gh->data[1];
+ uint8_t *classmark2 = gh->data+2;
+ uint8_t mi_len = *(classmark2 + classmark2_len);
+ uint8_t *mi = (classmark2 + classmark2_len + 1);
+
+ DEBUGP(DMM, "<- CM SERVICE REQUEST ");
+ if (msg->data_len < sizeof(struct gsm48_service_request*)) {
+ DEBUGPC(DMM, "wrong sized message\n");
+ return gsm48_tx_mm_serv_rej(conn,
+ GSM48_REJECT_INCORRECT_MESSAGE);
+ }
+
+ if (msg->data_len < req->mi_len + 6) {
+ DEBUGPC(DMM, "does not fit in packet\n");
+ return gsm48_tx_mm_serv_rej(conn,
+ GSM48_REJECT_INCORRECT_MESSAGE);
+ }
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+ mi_type = mi[0] & GSM_MI_TYPE_MASK;
+
+ if (mi_type == GSM_MI_TYPE_IMSI) {
+ DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n",
+ req->cm_service_type, gsm48_mi_type_name(mi_type),
+ mi_string);
+ subscr = subscr_get_by_imsi(network->subscr_group,
+ mi_string);
+ } else if (mi_type == GSM_MI_TYPE_TMSI) {
+ DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n",
+ req->cm_service_type, gsm48_mi_type_name(mi_type),
+ mi_string);
+ subscr = subscr_get_by_tmsi(network->subscr_group,
+ tmsi_from_string(mi_string));
+ } else {
+ DEBUGPC(DMM, "mi_type is not expected: %d\n", mi_type);
+ return gsm48_tx_mm_serv_rej(conn,
+ GSM48_REJECT_INCORRECT_MESSAGE);
+ }
+
+ osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len));
+
+ if (is_siemens_bts(conn->bts))
+ send_siemens_mrpci(msg->lchan, classmark2-1);
+
+
+ /* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */
+ if (!subscr)
+ return gsm48_tx_mm_serv_rej(conn,
+ GSM48_REJECT_IMSI_UNKNOWN_IN_VLR);
+
+ if (!conn->subscr)
+ conn->subscr = subscr;
+ else if (conn->subscr == subscr)
+ subscr_put(subscr); /* lchan already has a ref, don't need another one */
+ else {
+ DEBUGP(DMM, "<- CM Channel already owned by someone else?\n");
+ subscr_put(subscr);
+ }
+
+ subscr->equipment.classmark2_len = classmark2_len;
+ memcpy(subscr->equipment.classmark2, classmark2, classmark2_len);
+ db_sync_equipment(&subscr->equipment);
+
+ /* we will send a MM message soon */
+ conn->expire_timer_stopped = 1;
+
+ return gsm48_secure_channel(conn, req->cipher_key_seq,
+ _gsm48_rx_mm_serv_req_sec_cb, NULL);
+}
+
+static int gsm48_rx_mm_imsi_detach_ind(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm_network *network = conn->network;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_imsi_detach_ind *idi =
+ (struct gsm48_imsi_detach_ind *) gh->data;
+ uint8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK;
+ char mi_string[GSM48_MI_SIZE];
+ struct gsm_subscriber *subscr = NULL;
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len);
+ DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s",
+ gsm48_mi_type_name(mi_type), mi_string);
+
+ rate_ctr_inc(&network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ DEBUGPC(DMM, "\n");
+ subscr = subscr_get_by_tmsi(network->subscr_group,
+ tmsi_from_string(mi_string));
+ break;
+ case GSM_MI_TYPE_IMSI:
+ DEBUGPC(DMM, "\n");
+ subscr = subscr_get_by_imsi(network->subscr_group,
+ mi_string);
+ break;
+ case GSM_MI_TYPE_IMEI:
+ case GSM_MI_TYPE_IMEISV:
+ /* no sim card... FIXME: what to do ? */
+ DEBUGPC(DMM, ": unimplemented mobile identity type\n");
+ break;
+ default:
+ DEBUGPC(DMM, ": unknown mobile identity type\n");
+ break;
+ }
+
+ if (subscr) {
+ subscr_update(subscr, conn->bts,
+ GSM_SUBSCRIBER_UPDATE_DETACHED);
+ DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr));
+
+ subscr->equipment.classmark1 = idi->classmark1;
+ db_sync_equipment(&subscr->equipment);
+
+ subscr_put(subscr);
+ } else
+ DEBUGP(DMM, "Unknown Subscriber ?!?\n");
+
+ /* FIXME: iterate over all transactions and release them,
+ * imagine an IMSI DETACH happening during an active call! */
+
+ release_anchor(conn);
+ return 0;
+}
+
+static int gsm48_rx_mm_status(struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]);
+
+ return 0;
+}
+
+static int parse_gsm_auth_resp(uint8_t *res, uint8_t *res_len,
+ struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data;
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*ar)) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: MM AUTHENTICATION RESPONSE:"
+ " l3 length invalid: %u\n",
+ subscr_name(conn->subscr), msgb_l3len(msg));
+ return -EINVAL;
+ }
+
+ *res_len = sizeof(ar->sres);
+ memcpy(res, ar->sres, sizeof(ar->sres));
+ return 0;
+}
+
+static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len,
+ struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ uint8_t *data;
+ uint8_t iei;
+ uint8_t ie_len;
+ unsigned int data_len;
+
+ /* First parse the GSM part */
+ if (parse_gsm_auth_resp(res, res_len, conn, msg))
+ return -EINVAL;
+ OSMO_ASSERT(*res_len == 4);
+
+ /* Then add the extended res part */
+ gh = msgb_l3(msg);
+ data = gh->data + sizeof(struct gsm48_auth_resp);
+ data_len = msgb_l3len(msg) - (data - (uint8_t*)msgb_l3(msg));
+
+ if (data_len < 3) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: MM AUTHENTICATION RESPONSE:"
+ " l3 length invalid: %u\n",
+ subscr_name(conn->subscr), msgb_l3len(msg));
+ return -EINVAL;
+ }
+
+ iei = data[0];
+ ie_len = data[1];
+ if (iei != GSM48_IE_AUTH_RES_EXT) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: MM R99 AUTHENTICATION RESPONSE:"
+ " expected IEI 0x%02x, got 0x%02x\n",
+ subscr_name(conn->subscr),
+ GSM48_IE_AUTH_RES_EXT, iei);
+ return -EINVAL;
+ }
+
+ if (ie_len > 12) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: MM R99 AUTHENTICATION RESPONSE:"
+ " extended Auth Resp IE 0x%02x is too large: %u bytes\n",
+ subscr_name(conn->subscr), GSM48_IE_AUTH_RES_EXT, ie_len);
+ return -EINVAL;
+ }
+
+ *res_len += ie_len;
+ memcpy(res + 4, &data[2], ie_len);
+ return 0;
+}
+
+/* Chapter 9.2.3: Authentication Response */
+static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm_network *net = conn->network;
+ uint8_t res[16];
+ uint8_t res_len;
+ int rc;
+ bool is_r99;
+
+ if (!conn->subscr) {
+ LOGP(DMM, LOGL_ERROR,
+ "MM AUTHENTICATION RESPONSE: invalid: no subscriber\n");
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ if (msgb_l3len(msg) >
+ sizeof(struct gsm48_hdr) + sizeof(struct gsm48_auth_resp)) {
+ rc = parse_umts_auth_resp(res, &res_len, conn, msg);
+ is_r99 = true;
+ } else {
+ rc = parse_gsm_auth_resp(res, &res_len, conn, msg);
+ is_r99 = false;
+ }
+
+ if (rc) {
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ DEBUGP(DMM, "%s: MM %s AUTHENTICATION RESPONSE (%s = %s)\n",
+ subscr_name(conn->subscr),
+ is_r99 ? "R99" : "GSM", is_r99 ? "res" : "sres",
+ osmo_hexdump_nospc(res, res_len));
+
+ /* Future: vlr_sub_rx_auth_resp(conn->vsub, is_r99,
+ * conn->via_ran == RAN_UTRAN_IU,
+ * res, res_len);
+ */
+
+ if (res_len != 4) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: MM AUTHENTICATION RESPONSE:"
+ " UMTS authentication not supported\n",
+ subscr_name(conn->subscr));
+ }
+
+ /* Safety check */
+ if (!conn->sec_operation) {
+ DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n");
+ return -EIO;
+ }
+
+ /* Validate SRES */
+ if (memcmp(conn->sec_operation->atuple.vec.sres, res, 4)) {
+ int rc;
+ gsm_cbfn *cb = conn->sec_operation->cb;
+
+ DEBUGPC(DMM, "Invalid (expected %s)\n",
+ osmo_hexdump(conn->sec_operation->atuple.vec.sres, 4));
+
+ if (cb)
+ cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+ NULL, conn, conn->sec_operation->cb_data);
+
+ rc = gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return rc;
+ }
+
+ DEBUGPC(DMM, "OK\n");
+
+ /* Start ciphering */
+ return gsm0808_cipher_mode(conn, net->a5_encryption,
+ conn->sec_operation->atuple.vec.kc, 8, 0);
+}
+
+static int gsm48_rx_mm_auth_fail(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t cause;
+ uint8_t auts_tag;
+ uint8_t auts_len;
+ uint8_t *auts;
+ int rc;
+
+ if (!conn->sec_operation) {
+ DEBUGP(DMM, "%s: MM R99 AUTHENTICATION FAILURE:"
+ " No authentication/cipher operation in progress\n",
+ subscr_name(conn->subscr));
+ return -EINVAL;
+ }
+
+ if (!conn->subscr) {
+ LOGP(DMM, LOGL_ERROR,
+ "MM R99 AUTHENTICATION FAILURE: invalid: no subscriber\n");
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ if (msgb_l3len(msg) < sizeof(*gh) + 1) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: MM R99 AUTHENTICATION FAILURE:"
+ " l3 length invalid: %u\n",
+ subscr_name(conn->subscr), msgb_l3len(msg));
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ cause = gh->data[0];
+
+ if (cause != GSM48_REJECT_SYNCH_FAILURE) {
+ LOGP(DMM, LOGL_INFO,
+ "%s: MM R99 AUTHENTICATION FAILURE: cause 0x%0x\n",
+ subscr_name(conn->subscr), cause);
+ rc = gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return rc;
+ }
+
+ /* This is a Synch Failure procedure, which should pass an AUTS to
+ * resynchronize the sequence nr with the HLR. Expecting exactly one
+ * TLV with 14 bytes of AUTS. */
+
+ if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2) {
+ LOGP(DMM, LOGL_INFO,
+ "%s: MM R99 AUTHENTICATION FAILURE:"
+ " invalid Synch Failure: missing AUTS IE\n",
+ subscr_name(conn->subscr));
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ auts_tag = gh->data[1];
+ auts_len = gh->data[2];
+ auts = &gh->data[3];
+
+ if (auts_tag != GSM48_IE_AUTS
+ || auts_len != 14) {
+ LOGP(DMM, LOGL_INFO,
+ "%s: MM R99 AUTHENTICATION FAILURE:"
+ " invalid Synch Failure:"
+ " expected AUTS IE 0x%02x of 14 bytes,"
+ " got IE 0x%02x of %u bytes\n",
+ subscr_name(conn->subscr),
+ GSM48_IE_AUTS, auts_tag, auts_len);
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2 + auts_len) {
+ LOGP(DMM, LOGL_INFO,
+ "%s: MM R99 AUTHENTICATION FAILURE:"
+ " invalid Synch Failure msg: message truncated (%u)\n",
+ subscr_name(conn->subscr), msgb_l3len(msg));
+ gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return -EINVAL;
+ }
+
+ /* We have an AUTS IE with exactly 14 bytes of AUTS and the msgb is
+ * large enough. */
+
+ DEBUGP(DMM, "%s: MM R99 AUTHENTICATION SYNCH (AUTS = %s)\n",
+ subscr_name(conn->subscr), osmo_hexdump_nospc(auts, 14));
+
+ /* Future: vlr_sub_rx_auth_fail(conn->vsub, auts); */
+
+ LOGP(DMM, LOGL_ERROR, "%s: MM R99 AUTHENTICATION not supported\n",
+ subscr_name(conn->subscr));
+ rc = gsm48_tx_mm_auth_rej(conn);
+ release_security_operation(conn);
+ return rc;
+}
+
+/* Receive a GSM 04.08 Mobility Management (MM) message */
+static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int rc = 0;
+
+ switch (gsm48_hdr_msg_type(gh)) {
+ case GSM48_MT_MM_LOC_UPD_REQUEST:
+ DEBUGP(DMM, "LOCATION UPDATING REQUEST: ");
+ rc = mm_rx_loc_upd_req(conn, msg);
+ break;
+ case GSM48_MT_MM_ID_RESP:
+ rc = mm_rx_id_resp(conn, msg);
+ break;
+ case GSM48_MT_MM_CM_SERV_REQ:
+ rc = gsm48_rx_mm_serv_req(conn, msg);
+ break;
+ case GSM48_MT_MM_STATUS:
+ rc = gsm48_rx_mm_status(msg);
+ break;
+ case GSM48_MT_MM_TMSI_REALL_COMPL:
+ DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+ subscr_name(conn->subscr));
+ loc_updating_success(conn, 1);
+ break;
+ case GSM48_MT_MM_IMSI_DETACH_IND:
+ rc = gsm48_rx_mm_imsi_detach_ind(conn, msg);
+ break;
+ case GSM48_MT_MM_CM_REEST_REQ:
+ DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n");
+ break;
+ case GSM48_MT_MM_AUTH_RESP:
+ rc = gsm48_rx_mm_auth_resp(conn, msg);
+ break;
+ case GSM48_MT_MM_AUTH_FAIL:
+ rc = gsm48_rx_mm_auth_fail(conn, msg);
+ break;
+ default:
+ LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n",
+ gh->msg_type);
+ break;
+ }
+
+ return rc;
+}
+
+/* Receive a PAGING RESPONSE message from the MS */
+static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_pag_resp *resp;
+ uint8_t *classmark2_lv = gh->data + 1;
+ uint8_t mi_type;
+ char mi_string[GSM48_MI_SIZE];
+ struct gsm_subscriber *subscr = NULL;
+ struct bsc_subscr *bsub;
+ uint32_t tmsi;
+ int rc = 0;
+
+ resp = (struct gsm48_pag_resp *) &gh->data[0];
+ gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+ mi_string, &mi_type);
+ DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
+ gsm48_mi_type_name(mi_type), mi_string);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ tmsi = tmsi_from_string(mi_string);
+ subscr = subscr_get_by_tmsi(conn->network->subscr_group, tmsi);
+ break;
+ case GSM_MI_TYPE_IMSI:
+ subscr = subscr_get_by_imsi(conn->network->subscr_group,
+ mi_string);
+ break;
+ }
+
+ if (!subscr) {
+ DEBUGP(DRR, "<- Can't find any subscriber for this ID\n");
+ /* FIXME: request id? close channel? */
+ return -EINVAL;
+ }
+
+ if (!conn->subscr) {
+ conn->subscr = subscr;
+ } else if (conn->subscr != subscr) {
+ LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n");
+ subscr_put(subscr);
+ return -EINVAL;
+ } else {
+ DEBUGP(DRR, "<- Channel already owned by us\n");
+ subscr_put(subscr);
+ subscr = conn->subscr;
+ }
+
+ log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+ DEBUGP(DRR, "<- Channel was requested by %s\n",
+ subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi);
+
+ subscr->equipment.classmark2_len = *classmark2_lv;
+ memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv);
+ db_sync_equipment(&subscr->equipment);
+
+ /* TODO MSC split -- creating a BSC subscriber directly from MSC data
+ * structures in RAM. At some point the MSC will send a message to the
+ * BSC instead. */
+ bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers,
+ subscr->imsi);
+ bsub->tmsi = subscr->tmsi;
+ bsub->lac = subscr->lac;
+
+ /* We received a paging */
+ conn->expire_timer_stopped = 1;
+
+ rc = gsm48_handle_paging_resp(conn, msg, bsub);
+ return rc;
+}
+
+static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t apdu_id_flags;
+ uint8_t apdu_len;
+ uint8_t *apdu_data;
+
+ apdu_id_flags = gh->data[0];
+ apdu_len = gh->data[1];
+ apdu_data = gh->data+2;
+
+ DEBUGP(DRR, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s\n",
+ apdu_id_flags, apdu_len, osmo_hexdump(apdu_data, apdu_len));
+
+ return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data);
+}
+
+/* Receive a GSM 04.08 Radio Resource (RR) message */
+static int gsm0408_rcv_rr(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int rc = 0;
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_PAG_RESP:
+ rc = gsm48_rx_rr_pag_resp(conn, msg);
+ break;
+ case GSM48_MT_RR_APP_INFO:
+ rc = gsm48_rx_rr_app_info(conn, msg);
+ break;
+ default:
+ LOGP(DRR, LOGL_NOTICE, "MSC: Unimplemented %s GSM 04.08 RR "
+ "message\n", gsm48_rr_msg_name(gh->msg_type));
+ break;
+ }
+
+ return rc;
+}
+
+int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, uint8_t apdu_id,
+ uint8_t apdu_len, const uint8_t *apdu)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 APP INF");
+ struct gsm48_hdr *gh;
+
+ msg->lchan = conn->lchan;
+
+ DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n",
+ apdu_id, apdu_len);
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len);
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_APP_INFO;
+ gh->data[0] = apdu_id;
+ gh->data[1] = apdu_len;
+ memcpy(gh->data+2, apdu, apdu_len);
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* FIXME: this count_statistics is a state machine behaviour. we should convert
+ * the complete call control into a state machine. Afterwards we can move this
+ * code into state transitions.
+ */
+static void count_statistics(struct gsm_trans *trans, int new_state)
+{
+ int old_state = trans->cc.state;
+ struct rate_ctr_group *msc = trans->net->msc_ctrs;
+
+ if (old_state == new_state)
+ return;
+
+ /* state incoming */
+ switch (new_state) {
+ case GSM_CSTATE_ACTIVE:
+ osmo_counter_inc(trans->net->active_calls);
+ rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_ACTIVE]);
+ break;
+ }
+
+ /* state outgoing */
+ switch (old_state) {
+ case GSM_CSTATE_ACTIVE:
+ osmo_counter_dec(trans->net->active_calls);
+ if (new_state == GSM_CSTATE_DISCONNECT_REQ ||
+ new_state == GSM_CSTATE_DISCONNECT_IND)
+ rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_COMPLETE]);
+ else
+ rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_INCOMPLETE]);
+ break;
+ }
+}
+
+/* Call Control */
+
+/* The entire call control code is written in accordance with Figure 7.10c
+ * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE
+ * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY
+ * it for voice */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+ if (state > 31 || state < 0)
+ return;
+
+ DEBUGP(DCC, "new state %s -> %s\n",
+ gsm48_cc_state_name(trans->cc.state),
+ gsm48_cc_state_name(state));
+
+ count_statistics(trans, state);
+ trans->cc.state = state;
+}
+
+static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STATUS");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ uint8_t *cause, *call_state;
+
+ gh->msg_type = GSM48_MT_CC_STATUS;
+
+ cause = msgb_put(msg, 3);
+ cause[0] = 2;
+ cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER;
+ cause[2] = 0x80 | 30; /* response to status inquiry */
+
+ call_state = msgb_put(msg, 1);
+ call_state[0] = 0xc0 | 0x00;
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+ uint8_t pdisc, uint8_t msg_type)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 TX SIMPLE");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ msg->lchan = conn->lchan;
+
+ gh->proto_discr = pdisc;
+ gh->msg_type = msg_type;
+
+ return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+ if (osmo_timer_pending(&trans->cc.timer)) {
+ DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent);
+ osmo_timer_del(&trans->cc.timer);
+ trans->cc.Tcurrent = 0;
+ }
+}
+
+static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans,
+ int msg_type, struct gsm_mncc *mncc)
+{
+ struct msgb *msg;
+ unsigned char *data;
+
+ if (trans)
+ if (trans->conn && trans->conn->lchan)
+ DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+ "Sending '%s' to MNCC.\n",
+ trans->conn->lchan->ts->trx->bts->nr,
+ trans->conn->lchan->ts->trx->nr,
+ trans->conn->lchan->ts->nr, trans->transaction_id,
+ (trans->subscr)?(trans->subscr->extension):"-",
+ get_mncc_name(msg_type));
+ else
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+ "Sending '%s' to MNCC.\n",
+ (trans->subscr)?(trans->subscr->extension):"-",
+ get_mncc_name(msg_type));
+ else
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) "
+ "Sending '%s' to MNCC.\n", get_mncc_name(msg_type));
+
+ mncc->msg_type = msg_type;
+
+ msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+ if (!msg)
+ return -ENOMEM;
+
+ data = msgb_put(msg, sizeof(struct gsm_mncc));
+ memcpy(data, mncc, sizeof(struct gsm_mncc));
+
+ cc_tx_to_mncc(net, msg);
+
+ return 0;
+}
+
+int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans,
+ uint32_t callref, int location, int value)
+{
+ struct gsm_mncc rel;
+
+ memset(&rel, 0, sizeof(rel));
+ rel.callref = callref;
+ mncc_set_cause(&rel, location, value);
+ if (trans && trans->cc.state == GSM_CSTATE_RELEASE_REQ)
+ return mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+ return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+}
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF! */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+ gsm48_stop_cc_timer(trans);
+
+ /* send release to L4, if callref still exists */
+ if (trans->callref) {
+ /* Ressource unavailable */
+ mncc_release_ind(trans->net, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ }
+ if (trans->cc.state != GSM_CSTATE_NULL)
+ new_cc_state(trans, GSM_CSTATE_NULL);
+ if (trans->conn)
+ trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref);
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+
+/* call-back from paging the B-end of the connection */
+static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *_conn, void *_transt)
+{
+ struct gsm_subscriber_connection *conn = _conn;
+ struct gsm_trans *transt = _transt;
+
+ OSMO_ASSERT(!transt->conn);
+
+ /* check all tranactions (without lchan) for subscriber */
+ switch (event) {
+ case GSM_PAGING_SUCCEEDED:
+ DEBUGP(DCC, "Paging subscr %s succeeded!\n", transt->subscr->extension);
+ OSMO_ASSERT(conn);
+ /* Assign lchan */
+ transt->conn = conn;
+ /* send SETUP request to called party */
+ gsm48_cc_tx_setup(transt, &transt->cc.msg);
+ break;
+ case GSM_PAGING_EXPIRED:
+ case GSM_PAGING_BUSY:
+ DEBUGP(DCC, "Paging subscr %s expired!\n",
+ transt->subscr->extension);
+ /* Temporarily out of order */
+ mncc_release_ind(transt->net, transt,
+ transt->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_DEST_OOO);
+ transt->callref = 0;
+ transt->paging_request = NULL;
+ trans_free(transt);
+ break;
+ default:
+ LOGP(DCC, LOGL_ERROR, "Unknown paging event %d\n", event);
+ break;
+ }
+
+ transt->paging_request = NULL;
+ return 0;
+}
+
+static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable);
+
+/* handle audio path for handover */
+static int switch_for_handover(struct gsm_lchan *old_lchan,
+ struct gsm_lchan *new_lchan)
+{
+ struct rtp_socket *old_rs, *new_rs, *other_rs;
+
+ /* Ask the new socket to send to the already known port. */
+ if (new_lchan->conn->mncc_rtp_bridge) {
+ LOGP(DHO, LOGL_DEBUG, "Forwarding RTP\n");
+ rsl_ipacc_mdcx(new_lchan,
+ old_lchan->abis_ip.connect_ip,
+ old_lchan->abis_ip.connect_port, 0);
+ return 0;
+ }
+
+ if (ipacc_rtp_direct) {
+ LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n");
+ return 0;
+ }
+
+ /* RTP Proxy mode */
+ new_rs = new_lchan->abis_ip.rtp_socket;
+ old_rs = old_lchan->abis_ip.rtp_socket;
+
+ if (!new_rs) {
+ LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n");
+ return -EIO;
+ }
+
+ rsl_ipacc_mdcx_to_rtpsock(new_lchan);
+
+ if (!old_rs) {
+ LOGP(DHO, LOGL_ERROR, "no RTP socket for old_lchan\n");
+ return -EIO;
+ }
+
+ /* copy rx_action and reference to other sock */
+ new_rs->rx_action = old_rs->rx_action;
+ new_rs->tx_action = old_rs->tx_action;
+ new_rs->transmit = old_rs->transmit;
+
+ switch (old_lchan->abis_ip.rtp_socket->rx_action) {
+ case RTP_PROXY:
+ other_rs = old_rs->proxy.other_sock;
+ rtp_socket_proxy(new_rs, other_rs);
+ /* delete reference to other end socket to prevent
+ * rtp_socket_free() from removing the inverse reference */
+ old_rs->proxy.other_sock = NULL;
+ break;
+ case RTP_RECV_UPSTREAM:
+ new_rs->receive = old_rs->receive;
+ break;
+ case RTP_NONE:
+ break;
+ }
+
+ return 0;
+}
+
+static void maybe_switch_for_handover(struct gsm_lchan *lchan)
+{
+ struct gsm_lchan *old_lchan;
+ old_lchan = bsc_handover_pending(lchan);
+ if (old_lchan)
+ switch_for_handover(old_lchan, lchan);
+}
+
+/* some other part of the code sends us a signal */
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_lchan *lchan = signal_data;
+ int rc;
+ struct gsm_network *net;
+ struct gsm_trans *trans;
+
+ if (subsys != SS_ABISIP)
+ return 0;
+
+ /* RTP bridge handling */
+ if (lchan->conn && lchan->conn->mncc_rtp_bridge)
+ return tch_rtp_signal(lchan, signal);
+
+ /* in case we use direct BTS-to-BTS RTP */
+ if (ipacc_rtp_direct)
+ return 0;
+
+ switch (signal) {
+ case S_ABISIP_CRCX_ACK:
+ /* in case we don't use direct BTS-to-BTS RTP */
+ /* the BTS has successfully bound a TCH to a local ip/port,
+ * which means we can connect our UDP socket to it */
+ if (lchan->abis_ip.rtp_socket) {
+ rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ }
+
+ lchan->abis_ip.rtp_socket = rtp_socket_create();
+ if (!lchan->abis_ip.rtp_socket)
+ return -EIO;
+
+ rc = rtp_socket_connect(lchan->abis_ip.rtp_socket,
+ lchan->abis_ip.bound_ip,
+ lchan->abis_ip.bound_port);
+ if (rc < 0)
+ return -EIO;
+
+ /* check if any transactions on this lchan still have
+ * a tch_recv_mncc request pending */
+ net = lchan->ts->trx->bts->network;
+ llist_for_each_entry(trans, &net->trans_list, entry) {
+ if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) {
+ DEBUGP(DCC, "pending tch_recv_mncc request\n");
+ tch_recv_mncc(net, trans->callref, 1);
+ }
+ }
+
+ /*
+ * TODO: this appears to be too early? Why not until after
+ * the handover detect or the handover complete?
+ *
+ * Do we have a handover pending for this new lchan? In that
+ * case re-route the audio from the old channel to the new one.
+ */
+ maybe_switch_for_handover(lchan);
+ break;
+ case S_ABISIP_DLCX_IND:
+ /* the BTS tells us a RTP stream has been disconnected */
+ if (lchan->abis_ip.rtp_socket) {
+ rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+/* map two ipaccess RTP streams onto each other */
+static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts;
+ enum gsm_chan_t lt = lchan->type, rt = remote_lchan->type;
+ enum gsm48_chan_mode lm = lchan->tch_mode, rm = remote_lchan->tch_mode;
+ int rc;
+
+ DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u,%s) and "
+ "(bts=%u,trx=%u,ts=%u,%s)\n",
+ bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ get_value_string(gsm_chan_t_names, lt),
+ remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr,
+ get_value_string(gsm_chan_t_names, rt));
+
+ if (bts->type != remote_bts->type) {
+ LOGP(DCC, LOGL_ERROR, "Cannot switch calls between different BTS types yet\n");
+ return -EINVAL;
+ }
+
+ if (lt != rt) {
+ LOGP(DCC, LOGL_ERROR, "Cannot patch through call with different"
+ " channel types: local = %s, remote = %s\n",
+ get_value_string(gsm_chan_t_names, lt),
+ get_value_string(gsm_chan_t_names, rt));
+ return -EBADSLT;
+ }
+
+ if (lm != rm) {
+ LOGP(DCC, LOGL_ERROR, "Cannot patch through call with different"
+ " channel modes: local = %s, remote = %s\n",
+ get_value_string(gsm48_chan_mode_names, lm),
+ get_value_string(gsm48_chan_mode_names, rm));
+ return -EMEDIUMTYPE;
+ }
+
+ // todo: map between different bts types
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ if (!ipacc_rtp_direct) {
+ if (!lchan->abis_ip.rtp_socket) {
+ LOGP(DHO, LOGL_ERROR, "no RTP socket for "
+ "lchan\n");
+ return -EIO;
+ }
+ if (!remote_lchan->abis_ip.rtp_socket) {
+ LOGP(DHO, LOGL_ERROR, "no RTP socket for "
+ "remote_lchan\n");
+ return -EIO;
+ }
+
+ /* connect the TCH's to our RTP proxy */
+ rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+ if (rc < 0)
+ return rc;
+ rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan);
+ if (rc < 0)
+ return rc;
+ /* connect them with each other */
+ rtp_socket_proxy(lchan->abis_ip.rtp_socket,
+ remote_lchan->abis_ip.rtp_socket);
+ } else {
+ /* directly connect TCH RTP streams to each other */
+ rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip,
+ remote_lchan->abis_ip.bound_port,
+ remote_lchan->abis_ip.rtp_payload2);
+ if (rc < 0)
+ return rc;
+ rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip,
+ lchan->abis_ip.bound_port,
+ lchan->abis_ip.rtp_payload2);
+ }
+ break;
+ case GSM_BTS_TYPE_BS11:
+ case GSM_BTS_TYPE_RBS2000:
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ trau_mux_map_lchan(lchan, remote_lchan);
+ break;
+ default:
+ LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* bridge channels of two transactions */
+static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge)
+{
+ struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[0]);
+ struct gsm_trans *trans2 = trans_find_by_callref(net, bridge->callref[1]);
+
+ if (!trans1 || !trans2)
+ return -EIO;
+
+ if (!trans1->conn || !trans2->conn)
+ return -EIO;
+
+ /* Which subscriber do we want to track trans1 or trans2? */
+ log_set_context(LOG_CTX_VLR_SUBSCR, trans1->subscr);
+
+ /* through-connect channel */
+ return tch_map(trans1->conn->lchan, trans2->conn->lchan);
+}
+
+/* enable receive of channels to MNCC upqueue */
+static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable)
+{
+ struct gsm_trans *trans;
+ struct gsm_lchan *lchan;
+ struct gsm_bts *bts;
+ int rc;
+
+ /* Find callref */
+ trans = trans_find_by_callref(net, callref);
+ if (!trans)
+ return -EIO;
+ if (!trans->conn)
+ return 0;
+
+ log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+ lchan = trans->conn->lchan;
+ bts = lchan->ts->trx->bts;
+
+ /* store receive state */
+ trans->tch_recv = enable;
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ if (ipacc_rtp_direct) {
+ LOGP(DCC, LOGL_ERROR, "Error: RTP proxy is disabled\n");
+ return -EINVAL;
+ }
+ /* In case, we don't have a RTP socket to the BTS yet, the BTS
+ * will not be connected to our RTP proxy and the socket will
+ * not be assigned to the application interface. This method
+ * will be called again, once the audio socket is created and
+ * connected. */
+ if (!lchan->abis_ip.rtp_socket) {
+ DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+ return 0;
+ }
+ if (enable) {
+ /* connect the TCH's to our RTP proxy */
+ rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+ if (rc < 0)
+ return rc;
+ /* assign socket to application interface */
+ rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+ net, callref);
+ } else
+ rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+ net, 0);
+ break;
+ case GSM_BTS_TYPE_BS11:
+ case GSM_BTS_TYPE_RBS2000:
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ /* In case we don't have a TCH with correct mode, the TRAU muxer
+ * will not be asigned to the application interface. This is
+ * performed by switch_trau_mux() after successful handover or
+ * assignment. */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+ DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+ return 0;
+ }
+ if (enable)
+ return trau_recv_lchan(lchan, callref);
+ return trau_mux_unmap(NULL, callref);
+ break;
+ default:
+ LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+ DEBUGP(DCC, "-> STATUS ENQ\n");
+ return gsm48_cc_tx_status(trans, msg);
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+
+static void gsm48_cc_timeout(void *arg)
+{
+ struct gsm_trans *trans = arg;
+ int disconnect = 0, release = 0;
+ int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+ int mo_location = GSM48_CAUSE_LOC_USER;
+ int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+ int l4_location = GSM48_CAUSE_LOC_PRN_S_LU;
+ struct gsm_mncc mo_rel, l4_rel;
+
+ memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+ mo_rel.callref = trans->callref;
+ memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+ l4_rel.callref = trans->callref;
+
+ switch(trans->cc.Tcurrent) {
+ case 0x303:
+ release = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x310:
+ disconnect = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x313:
+ disconnect = 1;
+ /* unknown, did not find it in the specs */
+ break;
+ case 0x301:
+ disconnect = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x308:
+ if (!trans->cc.T308_second) {
+ /* restart T308 a second time */
+ gsm48_cc_tx_release(trans, &trans->cc.msg);
+ trans->cc.T308_second = 1;
+ break; /* stay in release state */
+ }
+ trans_free(trans);
+ return;
+// release = 1;
+// l4_cause = 14;
+// break;
+ case 0x306:
+ release = 1;
+ mo_cause = trans->cc.msg.cause.value;
+ mo_location = trans->cc.msg.cause.location;
+ break;
+ case 0x323:
+ disconnect = 1;
+ break;
+ default:
+ release = 1;
+ }
+
+ if (release && trans->callref) {
+ /* process release towards layer 4 */
+ mncc_release_ind(trans->net, trans, trans->callref,
+ l4_location, l4_cause);
+ trans->callref = 0;
+ }
+
+ if (disconnect && trans->callref) {
+ /* process disconnect towards layer 4 */
+ mncc_set_cause(&l4_rel, l4_location, l4_cause);
+ mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &l4_rel);
+ }
+
+ /* process disconnect towards mobile station */
+ if (disconnect || release) {
+ mncc_set_cause(&mo_rel, mo_location, mo_cause);
+ mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+ mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+ mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+ mo_rel.cause.diag_len = 3;
+
+ if (disconnect)
+ gsm48_cc_tx_disconnect(trans, &mo_rel);
+ if (release)
+ gsm48_cc_tx_release(trans, &mo_rel);
+ }
+
+}
+
+/* disconnect both calls from the bridge */
+static inline void disconnect_bridge(struct gsm_network *net,
+ struct gsm_mncc_bridge *bridge, int err)
+{
+ struct gsm_trans *trans0 = trans_find_by_callref(net, bridge->callref[0]);
+ struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[1]);
+ struct gsm_mncc mx_rel;
+ if (!trans0 || !trans1)
+ return;
+
+ DEBUGP(DCC, "Failed to bridge TCH for calls %x <-> %x :: %s \n",
+ trans0->callref, trans1->callref, strerror(err));
+
+ memset(&mx_rel, 0, sizeof(struct gsm_mncc));
+ mncc_set_cause(&mx_rel, GSM48_CAUSE_LOC_INN_NET,
+ GSM48_CC_CAUSE_CHAN_UNACCEPT);
+
+ mx_rel.callref = trans0->callref;
+ gsm48_cc_tx_disconnect(trans0, &mx_rel);
+
+ mx_rel.callref = trans1->callref;
+ gsm48_cc_tx_disconnect(trans1, &mx_rel);
+}
+
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+ int sec, int micro)
+{
+ DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec);
+ osmo_timer_setup(&trans->cc.timer, gsm48_cc_timeout, trans);
+ osmo_timer_schedule(&trans->cc.timer, sec, micro);
+ trans->cc.Tcurrent = current;
+}
+
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t msg_type = gsm48_hdr_msg_type(gh);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc setup;
+
+ memset(&setup, 0, sizeof(struct gsm_mncc));
+ setup.callref = trans->callref;
+ setup.lchan_type = trans->conn->lchan->type;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* emergency setup is identified by msg_type */
+ if (msg_type == GSM48_MT_CC_EMERG_SETUP)
+ setup.emergency = 1;
+
+ /* use subscriber as calling party number */
+ setup.fields |= MNCC_F_CALLING;
+ osmo_strlcpy(setup.calling.number, trans->subscr->extension,
+ sizeof(setup.calling.number));
+ osmo_strlcpy(setup.imsi, trans->subscr->imsi, sizeof(setup.imsi));
+
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ setup.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&setup.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ apply_codec_restrictions(trans->conn->bts, &setup.bearer_cap);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ setup.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&setup.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* called party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+ setup.fields |= MNCC_F_CALLED;
+ gsm48_decode_called(&setup.called,
+ TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ setup.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&setup.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ setup.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&setup.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+ /* CLIR suppression */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP))
+ setup.clir.sup = 1;
+ /* CLIR invocation */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC))
+ setup.clir.inv = 1;
+ /* cc cap */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+ setup.fields |= MNCC_F_CCCAP;
+ gsm48_decode_cccap(&setup.cccap,
+ TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+ LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n",
+ subscr_name(trans->subscr), trans->subscr->extension,
+ setup.called.number);
+
+ rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]);
+
+ /* indicate setup to MNCC */
+ mncc_recvmsg(trans->net, trans, MNCC_SETUP_IND, &setup);
+
+ /* MNCC code will modify the channel asynchronously, we should
+ * ipaccess-bind only after the modification has been made to the
+ * lchan->tch_mode */
+ return 0;
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STUP");
+ struct gsm48_hdr *gh;
+ struct gsm_mncc *setup = arg;
+ int rc, trans_id;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ /* transaction id must not be assigned */
+ if (trans->transaction_id != 0xff) { /* unasssigned */
+ DEBUGP(DCC, "TX Setup with assigned transaction. "
+ "This is not allowed!\n");
+ /* Temporarily out of order */
+ rc = mncc_release_ind(trans->net, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+
+ /* Get free transaction_id */
+ trans_id = trans_assign_trans_id(trans->net, trans->subscr,
+ GSM48_PDISC_CC, 0);
+ if (trans_id < 0) {
+ /* no free transaction ID */
+ rc = mncc_release_ind(trans->net, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+ trans->transaction_id = trans_id;
+
+ gh->msg_type = GSM48_MT_CC_SETUP;
+
+ gsm48_start_cc_timer(trans, 0x303, GSM48_T303);
+
+ /* bearer capability */
+ if (setup->fields & MNCC_F_BEARER_CAP)
+ gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap);
+ /* facility */
+ if (setup->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &setup->facility);
+ /* progress */
+ if (setup->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(msg, 0, &setup->progress);
+ /* calling party BCD number */
+ if (setup->fields & MNCC_F_CALLING)
+ gsm48_encode_calling(msg, &setup->calling);
+ /* called party BCD number */
+ if (setup->fields & MNCC_F_CALLED)
+ gsm48_encode_called(msg, &setup->called);
+ /* user-user */
+ if (setup->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &setup->useruser);
+ /* redirecting party BCD number */
+ if (setup->fields & MNCC_F_REDIRECTING)
+ gsm48_encode_redirecting(msg, &setup->redirecting);
+ /* signal */
+ if (setup->fields & MNCC_F_SIGNAL)
+ gsm48_encode_signal(msg, setup->signal);
+
+ new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+ rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP]);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc call_conf;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x310, GSM48_T310);
+
+ memset(&call_conf, 0, sizeof(struct gsm_mncc));
+ call_conf.callref = trans->callref;
+ call_conf.lchan_type = trans->conn->lchan->type;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+ /* repeat */
+ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+ call_conf.repeat = 1;
+ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+ call_conf.repeat = 2;
+#endif
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ call_conf.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&call_conf.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ apply_codec_restrictions(trans->conn->bts, &call_conf.bearer_cap);
+ }
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ call_conf.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&call_conf.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* cc cap */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+ call_conf.fields |= MNCC_F_CCCAP;
+ gsm48_decode_cccap(&call_conf.cccap,
+ TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+ }
+
+ /* IMSI of called subscriber */
+ osmo_strlcpy(call_conf.imsi, trans->subscr->imsi,
+ sizeof(call_conf.imsi));
+
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND,
+ &call_conf);
+}
+
+static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *proceeding = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROC");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CALL_PROC;
+
+ new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+ /* bearer capability */
+ if (proceeding->fields & MNCC_F_BEARER_CAP)
+ gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap);
+ /* facility */
+ if (proceeding->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &proceeding->facility);
+ /* progress */
+ if (proceeding->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(msg, 0, &proceeding->progress);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc alerting;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x301, GSM48_T301);
+
+ memset(&alerting, 0, sizeof(struct gsm_mncc));
+ alerting.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ alerting.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&alerting.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ alerting.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&alerting.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ alerting.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&alerting.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_ALERT_IND,
+ &alerting);
+}
+
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *alerting = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC ALERT");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_ALERTING;
+
+ /* facility */
+ if (alerting->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &alerting->facility);
+ /* progress */
+ if (alerting->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(msg, 0, &alerting->progress);
+ /* user-user */
+ if (alerting->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &alerting->useruser);
+
+ new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *progress = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROGRESS");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_PROGRESS;
+
+ /* progress */
+ gsm48_encode_progress(msg, 1, &progress->progress);
+ /* user-user */
+ if (progress->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &progress->useruser);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *connect = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSN 04.08 CC CON");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CONNECT;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x313, GSM48_T313);
+
+ /* facility */
+ if (connect->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &connect->facility);
+ /* progress */
+ if (connect->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(msg, 0, &connect->progress);
+ /* connected number */
+ if (connect->fields & MNCC_F_CONNECTED)
+ gsm48_encode_connected(msg, &connect->connected);
+ /* user-user */
+ if (connect->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &connect->useruser);
+
+ new_cc_state(trans, GSM_CSTATE_CONNECT_IND);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc connect;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&connect, 0, sizeof(struct gsm_mncc));
+ connect.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* use subscriber as connected party number */
+ connect.fields |= MNCC_F_CONNECTED;
+ osmo_strlcpy(connect.connected.number, trans->subscr->extension,
+ sizeof(connect.connected.number));
+ osmo_strlcpy(connect.imsi, trans->subscr->imsi, sizeof(connect.imsi));
+
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ connect.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&connect.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ connect.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&connect.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ connect.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&connect.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+ rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT]);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_SETUP_CNF, &connect);
+}
+
+
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc connect_ack;
+
+ gsm48_stop_cc_timer(trans);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+ rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK]);
+
+ memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+ connect_ack.callref = trans->callref;
+
+ return mncc_recvmsg(trans->net, trans, MNCC_SETUP_COMPL_IND,
+ &connect_ack);
+}
+
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC CON ACK");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc disc;
+
+ gsm48_stop_cc_timer(trans);
+
+ new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+ memset(&disc, 0, sizeof(struct gsm_mncc));
+ disc.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ disc.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&disc.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ disc.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&disc.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ disc.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&disc.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ disc.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&disc.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+
+ return mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &disc);
+
+}
+
+static struct gsm_mncc_cause default_cause = {
+ .location = GSM48_CAUSE_LOC_PRN_S_LU,
+ .coding = 0,
+ .rec = 0,
+ .rec_val = 0,
+ .value = GSM48_CC_CAUSE_NORMAL_UNSPEC,
+ .diag_len = 0,
+ .diag = { 0 },
+};
+
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *disc = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC DISC");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x306, GSM48_T306);
+
+ /* cause */
+ if (disc->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(msg, 1, &disc->cause);
+ else
+ gsm48_encode_cause(msg, 1, &default_cause);
+
+ /* facility */
+ if (disc->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &disc->facility);
+ /* progress */
+ if (disc->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(msg, 0, &disc->progress);
+ /* user-user */
+ if (disc->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &disc->useruser);
+
+ /* store disconnect cause for T306 expiry */
+ memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc));
+
+ new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc rel;
+ int rc;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ rel.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&rel.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ rel.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&rel.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ rel.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&rel.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ rel.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&rel.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+
+ if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+ /* release collision 5.4.5 */
+ rc = mncc_recvmsg(trans->net, trans, MNCC_REL_CNF, &rel);
+ } else {
+ rc = gsm48_tx_simple(trans->conn,
+ GSM48_PDISC_CC | (trans->transaction_id << 4),
+ GSM48_MT_CC_RELEASE_COMPL);
+ rc = mncc_recvmsg(trans->net, trans, MNCC_REL_IND, &rel);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_NULL);
+
+ trans->callref = 0;
+ trans_free(trans);
+
+ return rc;
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *rel = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC REL");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RELEASE;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x308, GSM48_T308);
+
+ /* cause */
+ if (rel->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(msg, 0, &rel->cause);
+ /* facility */
+ if (rel->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &rel->facility);
+ /* user-user */
+ if (rel->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+ trans->cc.T308_second = 0;
+ memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+ if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+ new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc rel;
+ int rc = 0;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ rel.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&rel.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ rel.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&rel.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ rel.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&rel.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ rel.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&rel.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+
+ if (trans->callref) {
+ switch (trans->cc.state) {
+ case GSM_CSTATE_CALL_PRESENT:
+ rc = mncc_recvmsg(trans->net, trans,
+ MNCC_REJ_IND, &rel);
+ break;
+ case GSM_CSTATE_RELEASE_REQ:
+ rc = mncc_recvmsg(trans->net, trans,
+ MNCC_REL_CNF, &rel);
+ break;
+ default:
+ rc = mncc_recvmsg(trans->net, trans,
+ MNCC_REL_IND, &rel);
+ }
+ }
+
+ trans->callref = 0;
+ trans_free(trans);
+
+ return rc;
+}
+
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *rel = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC REL COMPL");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ int ret;
+
+ gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+ trans->callref = 0;
+
+ gsm48_stop_cc_timer(trans);
+
+ /* cause */
+ if (rel->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(msg, 0, &rel->cause);
+ /* facility */
+ if (rel->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(msg, 0, &rel->facility);
+ /* user-user */
+ if (rel->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+ ret = gsm48_conn_sendmsg(msg, trans->conn, trans);
+
+ trans_free(trans);
+
+ return ret;
+}
+
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc fac;
+
+ memset(&fac, 0, sizeof(struct gsm_mncc));
+ fac.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0);
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ fac.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&fac.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* ss-version */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+ fac.fields |= MNCC_F_SSVERSION;
+ gsm48_decode_ssversion(&fac.ssversion,
+ TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+ }
+
+ return mncc_recvmsg(trans->net, trans, MNCC_FACILITY_IND, &fac);
+}
+
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *fac = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC FAC");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_FACILITY;
+
+ /* facility */
+ gsm48_encode_facility(msg, 1, &fac->facility);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc hold;
+
+ memset(&hold, 0, sizeof(struct gsm_mncc));
+ hold.callref = trans->callref;
+ return mncc_recvmsg(trans->net, trans, MNCC_HOLD_IND, &hold);
+}
+
+static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC HLD ACK");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_HOLD_ACK;
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *hold_rej = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC HLD REJ");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_HOLD_REJ;
+
+ /* cause */
+ if (hold_rej->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(msg, 1, &hold_rej->cause);
+ else
+ gsm48_encode_cause(msg, 1, &default_cause);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc retrieve;
+
+ memset(&retrieve, 0, sizeof(struct gsm_mncc));
+ retrieve.callref = trans->callref;
+ return mncc_recvmsg(trans->net, trans, MNCC_RETRIEVE_IND,
+ &retrieve);
+}
+
+static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC RETR ACK");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RETR_ACK;
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *retrieve_rej = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC RETR REJ");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RETR_REJ;
+
+ /* cause */
+ if (retrieve_rej->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(msg, 1, &retrieve_rej->cause);
+ else
+ gsm48_encode_cause(msg, 1, &default_cause);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc dtmf;
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* keypad facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+ dtmf.fields |= MNCC_F_KEYPAD;
+ gsm48_decode_keypad(&dtmf.keypad,
+ TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+ }
+
+ return mncc_recvmsg(trans->net, trans, MNCC_START_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *dtmf = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF ACK");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_START_DTMF_ACK;
+
+ /* keypad */
+ if (dtmf->fields & MNCC_F_KEYPAD)
+ gsm48_encode_keypad(msg, dtmf->keypad);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *dtmf = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF REJ");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_START_DTMF_REJ;
+
+ /* cause */
+ if (dtmf->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(msg, 1, &dtmf->cause);
+ else
+ gsm48_encode_cause(msg, 1, &default_cause);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF STP ACK");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK;
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc dtmf;
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+
+ return mncc_recvmsg(trans->net, trans, MNCC_STOP_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc modify;
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ modify.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&modify.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_IND, &modify);
+}
+
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY;
+
+ gsm48_start_cc_timer(trans, 0x323, GSM48_T323);
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc modify;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ modify.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&modify.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD COMPL");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc modify;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ modify.fields |= GSM48_IE_BEARER_CAP;
+ gsm48_decode_bearer_cap(&modify.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap);
+ }
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ modify.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&modify.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD REJ");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+ /* cause */
+ gsm48_encode_cause(msg, 1, &modify->cause);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *notify = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC NOT");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+ /* notify */
+ gsm48_encode_notify(msg, notify->notify);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+// struct tlv_parsed tp;
+ struct gsm_mncc notify;
+
+ memset(&notify, 0, sizeof(struct gsm_mncc));
+ notify.callref = trans->callref;
+// tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len);
+ if (payload_len >= 1)
+ gsm48_decode_notify(&notify.notify, gh->data);
+
+ return mncc_recvmsg(trans->net, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *user = arg;
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USR INFO");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+ /* user-user */
+ if (user->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(msg, 1, &user->useruser);
+ /* more data */
+ if (user->more)
+ gsm48_encode_more(msg);
+
+ return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc user;
+
+ memset(&user, 0, sizeof(struct gsm_mncc));
+ user.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0);
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ user.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&user.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+ /* more data */
+ if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+ user.more = 1;
+
+ return mncc_recvmsg(trans->net, trans, MNCC_USERINFO_IND, &user);
+}
+
+static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *mode = arg;
+ struct gsm_lchan *lchan = trans->conn->lchan;
+
+ /*
+ * We were forced to make an assignment a lot earlier and
+ * we should avoid sending another assignment that might
+ * even lead to a different kind of lchan (TCH/F vs. TCH/H).
+ * In case of rtp-bridge it is too late to change things
+ * here.
+ */
+ if (trans->conn->mncc_rtp_bridge && lchan->tch_mode != GSM48_CMODE_SIGN)
+ return 0;
+
+ return gsm0808_assign_req(trans->conn, mode->lchan_mode,
+ trans->conn->lchan->type != GSM_LCHAN_TCH_H);
+}
+
+static void mncc_recv_rtp(struct gsm_network *net, uint32_t callref,
+ int cmd, uint32_t addr, uint16_t port, uint32_t payload_type,
+ uint32_t payload_msg_type)
+{
+ uint8_t data[sizeof(struct gsm_mncc)];
+ struct gsm_mncc_rtp *rtp;
+
+ memset(&data, 0, sizeof(data));
+ rtp = (struct gsm_mncc_rtp *) &data[0];
+
+ rtp->callref = callref;
+ rtp->msg_type = cmd;
+ rtp->ip = addr;
+ rtp->port = port;
+ rtp->payload_type = payload_type;
+ rtp->payload_msg_type = payload_msg_type;
+ mncc_recvmsg(net, NULL, cmd, (struct gsm_mncc *)data);
+}
+
+static void mncc_recv_rtp_sock(struct gsm_network *net, struct gsm_trans *trans, int cmd)
+{
+ struct gsm_lchan *lchan;
+ int msg_type;
+
+ lchan = trans->conn->lchan;
+ switch (lchan->abis_ip.rtp_payload) {
+ case RTP_PT_GSM_FULL:
+ msg_type = GSM_TCHF_FRAME;
+ break;
+ case RTP_PT_GSM_EFR:
+ msg_type = GSM_TCHF_FRAME_EFR;
+ break;
+ case RTP_PT_GSM_HALF:
+ msg_type = GSM_TCHH_FRAME;
+ break;
+ case RTP_PT_AMR:
+ msg_type = GSM_TCH_FRAME_AMR;
+ break;
+ default:
+ LOGP(DMNCC, LOGL_ERROR, "%s unknown payload type %d\n",
+ gsm_lchan_name(lchan), lchan->abis_ip.rtp_payload);
+ msg_type = 0;
+ break;
+ }
+
+ return mncc_recv_rtp(net, trans->callref, cmd,
+ lchan->abis_ip.bound_ip,
+ lchan->abis_ip.bound_port,
+ lchan->abis_ip.rtp_payload,
+ msg_type);
+}
+
+static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd)
+{
+ return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0);
+}
+
+static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
+{
+ struct gsm_bts *bts;
+ struct gsm_lchan *lchan;
+ struct gsm_trans *trans;
+ enum gsm48_chan_mode m;
+
+ /* Find callref */
+ trans = trans_find_by_callref(net, callref);
+ if (!trans) {
+ LOGP(DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n");
+ mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+ return -EIO;
+ }
+ log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+ if (!trans->conn) {
+ LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
+ mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+ return 0;
+ }
+
+ lchan = trans->conn->lchan;
+ bts = lchan->ts->trx->bts;
+ if (!is_ipaccess_bts(bts)) {
+ /*
+ * I want this to be straight forward and have no audio flow
+ * through the nitb/osmo-mss system. This currently means that
+ * this will not work with BS11/Nokia type BTS. We would need
+ * to have a trau<->rtp bridge for these but still preferable
+ * in another process.
+ */
+ LOGP(DMNCC, LOGL_ERROR, "RTP create only works with IP systems\n");
+ mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+ return -EINVAL;
+ }
+
+ trans->conn->mncc_rtp_bridge = 1;
+ /*
+ * *sigh* we need to pick a codec now. Pick the most generic one
+ * right now and hope we could fix that later on. This is very
+ * similiar to the routine above.
+ * Fallback to the internal MNCC mode to select a route.
+ */
+ if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+ trans->conn->mncc_rtp_create_pending = 1;
+ m = mncc_codec_for_mode(lchan->type);
+ LOGP(DMNCC, LOGL_DEBUG, "RTP create: codec=%s, chan_type=%s\n",
+ get_value_string(gsm48_chan_mode_names, m),
+ get_value_string(gsm_chan_t_names, lchan->type));
+ return gsm0808_assign_req(trans->conn, m,
+ lchan->type != GSM_LCHAN_TCH_H);
+ }
+
+ mncc_recv_rtp_sock(trans->net, trans, MNCC_RTP_CREATE);
+ return 0;
+}
+
+static int tch_rtp_connect(struct gsm_network *net, void *arg)
+{
+ struct gsm_lchan *lchan;
+ struct gsm_trans *trans;
+ struct gsm_mncc_rtp *rtp = arg;
+
+ /* Find callref */
+ trans = trans_find_by_callref(net, rtp->callref);
+ if (!trans) {
+ LOGP(DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n");
+ mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+ return -EIO;
+ }
+ log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+ if (!trans->conn) {
+ LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n");
+ mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+ return 0;
+ }
+
+ lchan = trans->conn->lchan;
+ LOGP(DMNCC, LOGL_DEBUG, "RTP connect: codec=%s, chan_type=%s\n",
+ get_value_string(gsm48_chan_mode_names,
+ mncc_codec_for_mode(lchan->type)),
+ get_value_string(gsm_chan_t_names, lchan->type));
+
+ /* TODO: Check if payload_msg_type is compatible with what we have */
+ if (rtp->payload_type != lchan->abis_ip.rtp_payload) {
+ LOGP(DMNCC, LOGL_ERROR, "RTP connect with different RTP payload\n");
+ mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+ }
+
+ /*
+ * FIXME: payload2 can't be sent with MDCX as the osmo-bts code
+ * complains about both rtp and rtp payload2 being present in the
+ * same package!
+ */
+ trans->conn->mncc_rtp_connect_pending = 1;
+ return rsl_ipacc_mdcx(lchan, rtp->ip, rtp->port, 0);
+}
+
+static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
+{
+ struct gsm_network *net;
+ struct gsm_trans *tmp, *trans = NULL;
+
+ net = lchan->ts->trx->bts->network;
+ llist_for_each_entry(tmp, &net->trans_list, entry) {
+ if (!tmp->conn)
+ continue;
+ if (tmp->conn->lchan != lchan && tmp->conn->ho_lchan != lchan)
+ continue;
+ trans = tmp;
+ break;
+ }
+
+ if (!trans) {
+ LOGP(DMNCC, LOGL_ERROR, "%s IPA abis signal but no transaction.\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ switch (signal) {
+ case S_ABISIP_CRCX_ACK:
+ if (lchan->conn->mncc_rtp_create_pending) {
+ lchan->conn->mncc_rtp_create_pending = 0;
+ LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP create ind.\n",
+ gsm_lchan_name(lchan));
+ mncc_recv_rtp_sock(net, trans, MNCC_RTP_CREATE);
+ }
+ /*
+ * TODO: this appears to be too early? Why not until after
+ * the handover detect or the handover complete?
+ */
+ maybe_switch_for_handover(lchan);
+ break;
+ case S_ABISIP_MDCX_ACK:
+ if (lchan->conn->mncc_rtp_connect_pending) {
+ lchan->conn->mncc_rtp_connect_pending = 0;
+ LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP connect ind.\n",
+ gsm_lchan_name(lchan));
+ mncc_recv_rtp_sock(net, trans, MNCC_RTP_CONNECT);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+static struct downstate {
+ uint32_t states;
+ int type;
+ int (*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+ /* mobile originating call establishment */
+ {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */
+ MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc},
+ {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */
+ MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+ {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */
+ MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+ {SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */
+ MNCC_PROGRESS_REQ, gsm48_cc_tx_progress},
+ /* mobile terminating call establishment */
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+ MNCC_SETUP_REQ, gsm48_cc_tx_setup},
+ {SBIT(GSM_CSTATE_CONNECT_REQUEST),
+ MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack},
+ /* signalling during call */
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+ MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+ {ALL_STATES,
+ MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack},
+ {ALL_STATES,
+ MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej},
+ {ALL_STATES,
+ MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+ {SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+ MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+ {SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+ MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+ /* clearing */
+ {SBIT(GSM_CSTATE_INITIATED),
+ MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */
+ MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+ MNCC_REL_REQ, gsm48_cc_tx_release},
+ /* special */
+ {ALL_STATES,
+ MNCC_LCHAN_MODIFY, _gsm48_lchan_modify},
+};
+
+#define DOWNSLLEN \
+ (sizeof(downstatelist) / sizeof(struct downstate))
+
+
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
+{
+ int i, rc = 0;
+ struct gsm_trans *trans = NULL, *transt;
+ struct gsm_subscriber_connection *conn = NULL;
+ struct gsm_bts *bts = NULL;
+ struct gsm_mncc *data = arg, rel;
+
+ DEBUGP(DMNCC, "receive message %s\n", get_mncc_name(msg_type));
+
+ /* handle special messages */
+ switch(msg_type) {
+ case MNCC_BRIDGE:
+ rc = tch_bridge(net, arg);
+ if (rc < 0)
+ disconnect_bridge(net, arg, -rc);
+ return rc;
+ case MNCC_FRAME_DROP:
+ return tch_recv_mncc(net, data->callref, 0);
+ case MNCC_FRAME_RECV:
+ return tch_recv_mncc(net, data->callref, 1);
+ case MNCC_RTP_CREATE:
+ return tch_rtp_create(net, data->callref);
+ case MNCC_RTP_CONNECT:
+ return tch_rtp_connect(net, arg);
+ case MNCC_RTP_FREE:
+ /* unused right now */
+ return -EIO;
+ case GSM_TCHF_FRAME:
+ case GSM_TCHF_FRAME_EFR:
+ case GSM_TCHH_FRAME:
+ case GSM_TCH_FRAME_AMR:
+ /* Find callref */
+ trans = trans_find_by_callref(net, data->callref);
+ if (!trans) {
+ LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n");
+ return -EIO;
+ }
+ log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+ if (!trans->conn) {
+ LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n");
+ return 0;
+ }
+ if (!trans->conn->lchan) {
+ LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without lchan\n");
+ return 0;
+ }
+ if (trans->conn->lchan->type != GSM_LCHAN_TCH_F
+ && trans->conn->lchan->type != GSM_LCHAN_TCH_H) {
+ /* This should be LOGL_ERROR or NOTICE, but
+ * unfortuantely it happens for a couple of frames at
+ * the beginning of every RTP connection */
+ LOGP(DMNCC, LOGL_DEBUG, "TCH frame for lchan != TCH_F/TCH_H\n");
+ return 0;
+ }
+ bts = trans->conn->lchan->ts->trx->bts;
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ if (!trans->conn->lchan->abis_ip.rtp_socket) {
+ DEBUGP(DMNCC, "TCH frame to lchan without RTP connection\n");
+ return 0;
+ }
+ return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg);
+ case GSM_BTS_TYPE_BS11:
+ case GSM_BTS_TYPE_RBS2000:
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ return trau_send_frame(trans->conn->lchan, arg);
+ default:
+ LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type);
+ }
+ return -EINVAL;
+ }
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = data->callref;
+
+ /* Find callref */
+ trans = trans_find_by_callref(net, data->callref);
+
+ /* Callref unknown */
+ if (!trans) {
+ struct gsm_subscriber *subscr;
+
+ if (msg_type != MNCC_SETUP_REQ) {
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+ "Received '%s' from MNCC with "
+ "unknown callref %d\n", data->called.number,
+ get_mncc_name(msg_type), data->callref);
+ /* Invalid call reference */
+ return mncc_release_ind(net, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_INVAL_TRANS_ID);
+ }
+ if (!data->called.number[0] && !data->imsi[0]) {
+ DEBUGP(DCC, "(bts - trx - ts - ti) "
+ "Received '%s' from MNCC with "
+ "no number or IMSI\n", get_mncc_name(msg_type));
+ /* Invalid number */
+ return mncc_release_ind(net, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_INV_NR_FORMAT);
+ }
+ /* New transaction due to setup, find subscriber */
+ if (data->called.number[0])
+ subscr = subscr_get_by_extension(net->subscr_group,
+ data->called.number);
+ else
+ subscr = subscr_get_by_imsi(net->subscr_group,
+ data->imsi);
+
+ /* update the subscriber we deal with */
+ log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+
+ /* If subscriber is not found */
+ if (!subscr) {
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+ "Received '%s' from MNCC with "
+ "unknown subscriber %s\n", data->called.number,
+ get_mncc_name(msg_type), data->called.number);
+ /* Unknown subscriber */
+ return mncc_release_ind(net, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_UNASSIGNED_NR);
+ }
+ /* If subscriber is not "attached" */
+ if (!subscr->lac) {
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+ "Received '%s' from MNCC with "
+ "detached subscriber %s\n", data->called.number,
+ get_mncc_name(msg_type), data->called.number);
+ subscr_put(subscr);
+ /* Temporarily out of order */
+ return mncc_release_ind(net, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_DEST_OOO);
+ }
+ /* Create transaction */
+ trans = trans_alloc(net, subscr, GSM48_PDISC_CC, 0xff, data->callref);
+ if (!trans) {
+ DEBUGP(DCC, "No memory for trans.\n");
+ subscr_put(subscr);
+ /* Ressource unavailable */
+ mncc_release_ind(net, NULL, data->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ return -ENOMEM;
+ }
+ /* Find lchan */
+ conn = connection_for_subscr(subscr);
+
+ /* If subscriber has no lchan */
+ if (!conn) {
+ /* find transaction with this subscriber already paging */
+ llist_for_each_entry(transt, &net->trans_list, entry) {
+ /* Transaction of our lchan? */
+ if (transt == trans ||
+ transt->subscr != subscr)
+ continue;
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+ "Received '%s' from MNCC with "
+ "unallocated channel, paging already "
+ "started for lac %d.\n",
+ data->called.number,
+ get_mncc_name(msg_type), subscr->lac);
+ subscr_put(subscr);
+ trans_free(trans);
+ return 0;
+ }
+ /* store setup informations until paging was successfull */
+ memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+
+ /* Request a channel */
+ trans->paging_request = subscr_request_channel(subscr,
+ RSL_CHANNEED_TCH_F, setup_trig_pag_evt,
+ trans);
+ if (!trans->paging_request) {
+ LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n");
+ subscr_put(subscr);
+ trans_free(trans);
+ return 0;
+ }
+ subscr_put(subscr);
+ return 0;
+ }
+ /* Assign lchan */
+ trans->conn = conn;
+ subscr_put(subscr);
+ } else {
+ /* update the subscriber we deal with */
+ log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+ }
+
+ if (trans->conn)
+ conn = trans->conn;
+
+ /* if paging did not respond yet */
+ if (!conn) {
+ DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+ "Received '%s' from MNCC in paging state\n",
+ (trans->subscr)?(trans->subscr->extension):"-",
+ get_mncc_name(msg_type));
+ mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_NORM_CALL_CLEAR);
+ if (msg_type == MNCC_REL_REQ)
+ rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+ else
+ rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+
+ DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) "
+ "Received '%s' from MNCC in state %d (%s)\n",
+ conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+ trans->transaction_id,
+ (trans->conn->subscr)?(trans->conn->subscr->extension):"-",
+ get_mncc_name(msg_type), trans->cc.state,
+ gsm48_cc_state_name(trans->cc.state));
+
+ /* Find function for current state and message */
+ for (i = 0; i < DOWNSLLEN; i++)
+ if ((msg_type == downstatelist[i].type)
+ && ((1 << trans->cc.state) & downstatelist[i].states))
+ break;
+ if (i == DOWNSLLEN) {
+ DEBUGP(DCC, "Message unhandled at this state.\n");
+ return 0;
+ }
+
+ rc = downstatelist[i].rout(trans, arg);
+
+ return rc;
+}
+
+
+static struct datastate {
+ uint32_t states;
+ int type;
+ int (*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+ /* mobile originating call establishment */
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+ GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+ GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup},
+ {SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */
+ GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+ /* mobile terminating call establishment */
+ {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */
+ GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf},
+ {SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */
+ GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+ {SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */
+ GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+ /* signalling during call */
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL),
+ GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+ {ALL_STATES,
+ GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf},
+ {ALL_STATES,
+ GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf},
+ {ALL_STATES,
+ GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_HOLD, gsm48_cc_rx_hold},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+ {SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+ GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+ {SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+ GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+ /* clearing */
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+ GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */
+ GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+ {ALL_STATES, /* 5.4.3.4 */
+ GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+};
+
+#define DATASLLEN \
+ (sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t msg_type = gsm48_hdr_msg_type(gh);
+ uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
+ struct gsm_trans *trans = NULL;
+ int i, rc = 0;
+
+ if (msg_type & 0x80) {
+ DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type);
+ return -EINVAL;
+ }
+
+ if (!conn->subscr) {
+ LOGP(DCC, LOGL_ERROR, "Invalid conn, no subscriber\n");
+ return -EINVAL;
+ }
+
+ /* Find transaction */
+ trans = trans_find_by_id(conn, GSM48_PDISC_CC, transaction_id);
+
+ DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+ "Received '%s' from MS in state %d (%s)\n",
+ conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+ transaction_id, (conn->subscr)?(conn->subscr->extension):"-",
+ gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
+ gsm48_cc_state_name(trans?(trans->cc.state):0));
+
+ /* Create transaction */
+ if (!trans) {
+ DEBUGP(DCC, "Unknown transaction ID %x, "
+ "creating new trans.\n", transaction_id);
+ /* Create transaction */
+ trans = trans_alloc(conn->network, conn->subscr,
+ GSM48_PDISC_CC,
+ transaction_id, new_callref++);
+ if (!trans) {
+ DEBUGP(DCC, "No memory for trans.\n");
+ rc = gsm48_tx_simple(conn,
+ GSM48_PDISC_CC | (transaction_id << 4),
+ GSM48_MT_CC_RELEASE_COMPL);
+ return -ENOMEM;
+ }
+ /* Assign transaction */
+ trans->conn = conn;
+ }
+
+ /* find function for current state and message */
+ for (i = 0; i < DATASLLEN; i++)
+ if ((msg_type == datastatelist[i].type)
+ && ((1 << trans->cc.state) & datastatelist[i].states))
+ break;
+ if (i == DATASLLEN) {
+ DEBUGP(DCC, "Message unhandled at this state.\n");
+ return 0;
+ }
+
+ assert(trans->subscr);
+
+ rc = datastatelist[i].rout(trans, msg);
+
+ return rc;
+}
+
+/* Create a dummy to wait five seconds */
+static void release_anchor(struct gsm_subscriber_connection *conn)
+{
+ if (!conn->anch_operation)
+ return;
+
+ osmo_timer_del(&conn->anch_operation->timeout);
+ talloc_free(conn->anch_operation);
+ conn->anch_operation = NULL;
+}
+
+static void anchor_timeout(void *_data)
+{
+ struct gsm_subscriber_connection *con = _data;
+
+ release_anchor(con);
+ msc_release_connection(con);
+}
+
+int gsm0408_new_conn(struct gsm_subscriber_connection *conn)
+{
+ conn->anch_operation = talloc_zero(conn, struct gsm_anchor_operation);
+ if (!conn->anch_operation)
+ return -1;
+
+ osmo_timer_setup(&conn->anch_operation->timeout, anchor_timeout, conn);
+ osmo_timer_schedule(&conn->anch_operation->timeout, 5, 0);
+ return 0;
+}
+
+struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network)
+{
+ struct gsm_subscriber_connection *conn;
+
+ conn = talloc_zero(network, struct gsm_subscriber_connection);
+ if (!conn)
+ return NULL;
+
+ conn->network = network;
+ llist_add_tail(&conn->entry, &network->subscr_conns);
+ return conn;
+}
+
+void msc_subscr_con_free(struct gsm_subscriber_connection *conn)
+{
+ if (!conn)
+ return;
+
+ if (conn->subscr) {
+ subscr_put(conn->subscr);
+ conn->subscr = NULL;
+ }
+
+ llist_del(&conn->entry);
+ talloc_free(conn);
+}
+
+/* Main entry point for GSM 04.08/44.008 Layer 3 data (e.g. from the BSC). */
+int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ int rc = 0;
+
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(msg);
+
+ LOGP(DRLL, LOGL_DEBUG, "Dispatching 04.08 message, pdisc=%d\n", pdisc);
+#if 0
+ if (silent_call_reroute(conn, msg))
+ return silent_call_rx(conn, msg);
+#endif
+
+ switch (pdisc) {
+ case GSM48_PDISC_CC:
+ release_anchor(conn);
+ rc = gsm0408_rcv_cc(conn, msg);
+ break;
+ case GSM48_PDISC_MM:
+ rc = gsm0408_rcv_mm(conn, msg);
+ break;
+ case GSM48_PDISC_RR:
+ rc = gsm0408_rcv_rr(conn, msg);
+ break;
+ case GSM48_PDISC_SMS:
+ release_anchor(conn);
+ rc = gsm0411_rcv_sms(conn, msg);
+ break;
+ case GSM48_PDISC_MM_GPRS:
+ case GSM48_PDISC_SM_GPRS:
+ LOGP(DRLL, LOGL_NOTICE, "Unimplemented "
+ "GSM 04.08 discriminator 0x%02x\n", pdisc);
+ rc = -ENOTSUP;
+ break;
+ case GSM48_PDISC_NC_SS:
+ release_anchor(conn);
+ rc = handle_rcv_ussd(conn, msg);
+ break;
+ default:
+ LOGP(DRLL, LOGL_NOTICE, "Unknown "
+ "GSM 04.08 discriminator 0x%02x\n", pdisc);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * This will be run by the linker when loading the DSO. We use it to
+ * do system initialization, e.g. registration of signal handlers.
+ */
+static __attribute__((constructor)) void on_dso_load_0408(void)
+{
+ osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, NULL);
+}
diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c
new file mode 100644
index 000000000..aa2030f80
--- /dev/null
+++ b/src/libmsc/gsm_04_11.c
@@ -0,0 +1,1070 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2011 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 Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include "bscconfig.h"
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsm0411_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+#ifdef BUILD_SMPP
+#include "smpp_smsc.h"
+#endif
+
+void *tall_gsms_ctx;
+static uint32_t new_callref = 0x40000001;
+
+
+struct gsm_sms *sms_alloc(void)
+{
+ return talloc_zero(tall_gsms_ctx, struct gsm_sms);
+}
+
+void sms_free(struct gsm_sms *sms)
+{
+ /* drop references to subscriber structure */
+ if (sms->receiver)
+ subscr_put(sms->receiver);
+#ifdef BUILD_SMPP
+ if (sms->smpp.esme)
+ smpp_esme_put(sms->smpp.esme);
+#endif
+
+ talloc_free(sms);
+}
+
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver,
+ struct gsm_subscriber *sender,
+ int dcs, const char *text)
+{
+ struct gsm_sms *sms = sms_alloc();
+
+ if (!sms)
+ return NULL;
+
+ sms->receiver = subscr_get(receiver);
+ osmo_strlcpy(sms->text, text, sizeof(sms->text));
+
+ osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr));
+ sms->reply_path_req = 0;
+ sms->status_rep_req = 0;
+ sms->ud_hdr_ind = 0;
+ sms->protocol_id = 0; /* implicit */
+ sms->data_coding_scheme = dcs;
+ osmo_strlcpy(sms->dst.addr, receiver->extension, sizeof(sms->dst.addr));
+ /* Generate user_data */
+ sms->user_data_len = gsm_7bit_encode_n(sms->user_data, sizeof(sms->user_data),
+ sms->text, NULL);
+
+ return sms;
+}
+
+
+static void send_signal(int sig_no,
+ struct gsm_trans *trans,
+ struct gsm_sms *sms,
+ int paging_result)
+{
+ struct sms_signal_data sig;
+ sig.trans = trans;
+ sig.sms = sms;
+ sig.paging_result = paging_result;
+ osmo_signal_dispatch(SS_SMS, sig_no, &sig);
+}
+
+static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ DEBUGP(DLSMS, "GSM4.11 TX %s\n", osmo_hexdump(msg->data, msg->len));
+ msg->l3h = msg->data;
+ return gsm0808_submit_dtap(conn, msg, UM_SAPI_SMS, 1);
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+ uint8_t msg_type)
+{
+ struct gsm48_hdr *gh;
+
+ gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+ /* Outgoing needs the highest bit set */
+ gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
+ gh->msg_type = msg_type;
+
+ DEBUGP(DLSMS, "sending CP message (trans=%x)\n", trans->transaction_id);
+
+ return gsm411_sendmsg(trans->conn, msg);
+}
+
+/* mm_send: receive MMCCSMS sap message from SMC */
+static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type,
+ struct msgb *msg, int cp_msg_type)
+{
+ struct gsm_trans *trans =
+ container_of(inst, struct gsm_trans, sms.smc_inst);
+ int rc = 0;
+
+ switch (msg_type) {
+ case GSM411_MMSMS_EST_REQ:
+ /* recycle msg */
+ rc = gsm411_smc_recv(inst, GSM411_MMSMS_EST_CNF, msg, 0);
+ msgb_free(msg); /* upper layer does not free msg */
+ break;
+ case GSM411_MMSMS_DATA_REQ:
+ rc = gsm411_cp_sendmsg(msg, trans, cp_msg_type);
+ break;
+ case GSM411_MMSMS_REL_REQ:
+ DEBUGP(DLSMS, "Got MMSMS_REL_REQ, destroying transaction.\n");
+ msgb_free(msg);
+ trans_free(trans);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Unhandled MMCCSMS msg 0x%x\n", msg_type);
+ msgb_free(msg);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* mm_send: receive MNCCSMS sap message from SMR */
+int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type,
+ struct msgb *msg)
+{
+ struct gsm_trans *trans =
+ container_of(inst, struct gsm_trans, sms.smr_inst);
+
+ /* forward to SMC */
+ return gsm411_smc_send(&trans->sms.smc_inst, msg_type, msg);
+}
+
+static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms)
+{
+ if (db_sms_store(gsms) != 0) {
+ LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+ return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+ }
+ /* dispatch a signal to tell higher level about it */
+ send_signal(S_SMS_SUBMITTED, NULL, gsms, 0);
+
+ return 0;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+static int gsm340_gen_oa_sub(uint8_t *oa, unsigned int oa_len,
+ const struct gsm_sms_addr *src)
+{
+ /* network specific, private numbering plan */
+ return gsm340_gen_oa(oa, oa_len, src->ton, src->npi, src->addr);
+}
+
+/* generate a msgb containing an 03.40 9.2.2.1 SMS-DELIVER TPDU derived from
+ * struct gsm_sms, returns total size of TPDU */
+static int gsm340_gen_sms_deliver_tpdu(struct msgb *msg, struct gsm_sms *sms)
+{
+ uint8_t *smsp;
+ uint8_t oa[12]; /* max len per 03.40 */
+ uint8_t oa_len = 0;
+ uint8_t octet_len;
+ unsigned int old_msg_len = msg->len;
+
+ /* generate first octet with masked bits */
+ smsp = msgb_put(msg, 1);
+ /* TP-MTI (message type indicator) */
+ *smsp = GSM340_SMS_DELIVER_SC2MS;
+ /* TP-MMS (more messages to send) */
+ if (0 /* FIXME */)
+ *smsp |= 0x04;
+ /* TP-SRI(deliver)/SRR(submit) */
+ if (sms->status_rep_req)
+ *smsp |= 0x20;
+ /* TP-UDHI (indicating TP-UD contains a header) */
+ if (sms->ud_hdr_ind)
+ *smsp |= 0x40;
+
+ /* generate originator address */
+ oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), &sms->src);
+ smsp = msgb_put(msg, oa_len);
+ memcpy(smsp, oa, oa_len);
+
+ /* generate TP-PID */
+ smsp = msgb_put(msg, 1);
+ *smsp = sms->protocol_id;
+
+ /* generate TP-DCS */
+ smsp = msgb_put(msg, 1);
+ *smsp = sms->data_coding_scheme;
+
+ /* generate TP-SCTS */
+ smsp = msgb_put(msg, 7);
+ gsm340_gen_scts(smsp, time(NULL));
+
+ /* generate TP-UDL */
+ smsp = msgb_put(msg, 1);
+ *smsp = sms->user_data_len;
+
+ /* generate TP-UD */
+ switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) {
+ case DCS_7BIT_DEFAULT:
+ octet_len = sms->user_data_len*7/8;
+ if (sms->user_data_len*7%8 != 0)
+ octet_len++;
+ /* Warning, user_data_len indicates the amount of septets
+ * (characters), we need amount of octets occupied */
+ smsp = msgb_put(msg, octet_len);
+ memcpy(smsp, sms->user_data, octet_len);
+ break;
+ case DCS_UCS2:
+ case DCS_8BIT_DATA:
+ smsp = msgb_put(msg, sms->user_data_len);
+ memcpy(smsp, sms->user_data, sms->user_data_len);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n",
+ sms->data_coding_scheme);
+ break;
+ }
+
+ return msg->len - old_msg_len;
+}
+
+int sms_route_mt_sms(struct gsm_subscriber_connection *conn, struct msgb *msg,
+ struct gsm_sms *gsms, uint8_t sms_mti, bool *deferred)
+{
+ int rc;
+
+#ifdef BUILD_SMPP
+ int smpp_first = smpp_route_smpp_first(gsms, conn);
+
+ /*
+ * Route through SMPP first before going to the local database. In case
+ * of a unroutable message and no local subscriber, SMPP will be tried
+ * twice. In case of an unknown subscriber continue with the normal
+ * delivery of the SMS.
+ */
+ if (smpp_first) {
+ rc = smpp_try_deliver(gsms, conn, deferred);
+ if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED)
+ goto try_local;
+ if (rc < 0) {
+ LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.",
+ subscr_name(conn->subscr), rc);
+ rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
+ /* rc will be logged by gsm411_send_rp_error() */
+ rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[
+ MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
+ }
+ return rc;
+ }
+
+try_local:
+#endif
+
+ /* determine gsms->receiver based on dialled number */
+ gsms->receiver = subscr_get_by_extension(conn->network->subscr_group,
+ gsms->dst.addr);
+ if (!gsms->receiver) {
+#ifdef BUILD_SMPP
+ /* Avoid a second look-up */
+ if (smpp_first) {
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+ return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+ }
+
+ rc = smpp_try_deliver(gsms, conn, deferred);
+ if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) {
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+ } else if (rc < 0) {
+ LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.",
+ subscr_name(conn->subscr), rc);
+ rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
+ /* rc will be logged by gsm411_send_rp_error() */
+ rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[
+ MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
+ }
+#else
+ rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+#endif
+ return rc;
+ }
+
+ switch (sms_mti) {
+ case GSM340_SMS_SUBMIT_MS2SC:
+ /* MS is submitting a SMS */
+ rc = gsm340_rx_sms_submit(msg, gsms);
+ break;
+ case GSM340_SMS_COMMAND_MS2SC:
+ case GSM340_SMS_DELIVER_REP_MS2SC:
+ LOGP(DLSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti);
+ rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti);
+ rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+ break;
+ }
+
+ if (!rc && !gsms->receiver)
+ rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+
+ return rc;
+}
+
+
+/* process an incoming TPDU (called from RP-DATA)
+ * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */
+static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
+ uint32_t gsm411_msg_ref, bool *deferred)
+{
+ struct gsm_subscriber_connection *conn = trans->conn;
+ uint8_t *smsp = msgb_sms(msg);
+ struct gsm_sms *gsms;
+ unsigned int sms_alphabet;
+ uint8_t sms_mti, sms_vpf;
+ uint8_t *sms_vp;
+ uint8_t da_len_bytes;
+ uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
+ int rc = 0;
+
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]);
+
+ gsms = sms_alloc();
+ if (!gsms)
+ return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+ /* invert those fields where 0 means active/present */
+ sms_mti = *smsp & 0x03;
+ sms_vpf = (*smsp & 0x18) >> 3;
+ gsms->status_rep_req = (*smsp & 0x20);
+ gsms->ud_hdr_ind = (*smsp & 0x40);
+ /*
+ * Not evaluating MMS (More Messages to Send) because the
+ * lchan stays open anyway.
+ * Not evaluating RP (Reply Path) because we're not aware of its
+ * benefits.
+ */
+
+ smsp++;
+ gsms->msg_ref = *smsp++;
+
+ gsms->gsm411.transaction_id = trans->transaction_id;
+ gsms->gsm411.msg_ref = gsm411_msg_ref;
+
+ /* length in bytes of the destination address */
+ da_len_bytes = 2 + *smsp/2 + *smsp%2;
+ if (da_len_bytes > 12) {
+ LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
+ rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+ goto out;
+ } else if (da_len_bytes < 4) {
+ LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n");
+ rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+ goto out;
+ }
+ memset(address_lv, 0, sizeof(address_lv));
+ memcpy(address_lv, smsp, da_len_bytes);
+ /* mangle first byte to reflect length in bytes, not digits */
+ address_lv[0] = da_len_bytes - 1;
+
+ gsms->dst.ton = (address_lv[1] >> 4) & 7;
+ gsms->dst.npi = address_lv[1] & 0xF;
+ /* convert to real number */
+ gsm48_decode_bcd_number(gsms->dst.addr,
+ sizeof(gsms->dst.addr), address_lv, 1);
+ smsp += da_len_bytes;
+
+ gsms->protocol_id = *smsp++;
+ gsms->data_coding_scheme = *smsp++;
+
+ sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme);
+ if (sms_alphabet == 0xffffffff) {
+ rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+ goto out;
+ }
+
+ switch (sms_vpf) {
+ case GSM340_TP_VPF_RELATIVE:
+ sms_vp = smsp++;
+ break;
+ case GSM340_TP_VPF_ABSOLUTE:
+ case GSM340_TP_VPF_ENHANCED:
+ sms_vp = smsp;
+ /* the additional functionality indicator... */
+ if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
+ smsp++;
+ smsp += 7;
+ break;
+ case GSM340_TP_VPF_NONE:
+ sms_vp = 0;
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE,
+ "SMS Validity period not implemented: 0x%02x\n", sms_vpf);
+ rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+ goto out;
+ }
+ gsms->user_data_len = *smsp++;
+ if (gsms->user_data_len) {
+ memcpy(gsms->user_data, smsp, gsms->user_data_len);
+
+ switch (sms_alphabet) {
+ case DCS_7BIT_DEFAULT:
+ gsm_7bit_decode_n(gsms->text, sizeof(gsms->text), smsp,
+ gsms->user_data_len);
+ break;
+ case DCS_8BIT_DATA:
+ case DCS_UCS2:
+ case DCS_NONE:
+ break;
+ }
+ }
+
+ osmo_strlcpy(gsms->src.addr, conn->subscr->extension,
+ sizeof(gsms->src.addr));
+
+ LOGP(DLSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, "
+ "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
+ "UserDataLength: 0x%02x, UserData: \"%s\"\n",
+ subscr_name(conn->subscr), sms_mti, sms_vpf, gsms->msg_ref,
+ gsms->protocol_id, gsms->data_coding_scheme, gsms->dst.addr,
+ gsms->user_data_len,
+ sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
+ osmo_hexdump(gsms->user_data, gsms->user_data_len));
+
+ gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
+
+ /* FIXME: This looks very wrong */
+ send_signal(0, NULL, gsms, 0);
+
+ rc = sms_route_mt_sms(conn, msg, gsms, sms_mti, deferred);
+out:
+ if (!deferred)
+ sms_free(gsms);
+
+ return rc;
+}
+
+/* Prefix msg with a RP-DATA header and send as SMR DATA */
+static int gsm411_rp_sendmsg(struct gsm411_smr_inst *inst, struct msgb *msg,
+ uint8_t rp_msg_type, uint8_t rp_msg_ref,
+ int rl_msg_type)
+{
+ struct gsm411_rp_hdr *rp;
+ uint8_t len = msg->len;
+
+ /* GSM 04.11 RP-DATA header */
+ rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+ rp->len = len + 2;
+ rp->msg_type = rp_msg_type;
+ rp->msg_ref = rp_msg_ref;
+
+ return gsm411_smr_send(inst, rl_msg_type, msg);
+}
+
+int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref)
+{
+ struct msgb *msg = gsm411_msgb_alloc();
+
+ DEBUGP(DLSMS, "TX: SMS RP ACK\n");
+
+ return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, GSM411_MT_RP_ACK_MT,
+ msg_ref, GSM411_SM_RL_REPORT_REQ);
+}
+
+int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref,
+ uint8_t cause)
+{
+ struct msgb *msg = gsm411_msgb_alloc();
+
+ msgb_tv_put(msg, 1, cause);
+
+ LOGP(DLSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause,
+ get_value_string(gsm411_rp_cause_strs, cause));
+
+ return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg,
+ GSM411_MT_RP_ERROR_MT, msg_ref, GSM411_SM_RL_REPORT_REQ);
+}
+
+/* Receive a 04.11 TPDU inside RP-DATA / user data */
+static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans,
+ struct gsm411_rp_hdr *rph,
+ uint8_t src_len, uint8_t *src,
+ uint8_t dst_len, uint8_t *dst,
+ uint8_t tpdu_len, uint8_t *tpdu)
+{
+ bool deferred = false;
+ int rc = 0;
+
+ if (src_len && src)
+ LOGP(DLSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n");
+
+ if (!dst_len || !dst || !tpdu_len || !tpdu) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "RP-DATA (MO) without DST or TPDU ?!?\n");
+ gsm411_send_rp_error(trans, rph->msg_ref,
+ GSM411_RP_CAUSE_INV_MAND_INF);
+ return -EIO;
+ }
+ msg->l4h = tpdu;
+
+ DEBUGP(DLSMS, "DST(%u,%s)\n", dst_len, osmo_hexdump(dst, dst_len));
+
+ rc = gsm340_rx_tpdu(trans, msg, rph->msg_ref, &deferred);
+ if (rc == 0 && !deferred)
+ return gsm411_send_rp_ack(trans, rph->msg_ref);
+ else if (rc > 0)
+ return gsm411_send_rp_error(trans, rph->msg_ref, rc);
+ else
+ return rc;
+}
+
+/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */
+static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans,
+ struct gsm411_rp_hdr *rph)
+{
+ uint8_t src_len, dst_len, rpud_len;
+ uint8_t *src = NULL, *dst = NULL , *rp_ud = NULL;
+
+ /* in the MO case, this should always be zero length */
+ src_len = rph->data[0];
+ if (src_len)
+ src = &rph->data[1];
+
+ dst_len = rph->data[1+src_len];
+ if (dst_len)
+ dst = &rph->data[1+src_len+1];
+
+ rpud_len = rph->data[1+src_len+1+dst_len];
+ if (rpud_len)
+ rp_ud = &rph->data[1+src_len+1+dst_len+1];
+
+ DEBUGP(DLSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n",
+ src_len, dst_len, rpud_len);
+ return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst,
+ rpud_len, rp_ud);
+}
+
+/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */
+static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans,
+ struct gsm411_rp_hdr *rph)
+{
+ struct gsm_sms *sms = trans->sms.sms;
+
+ /* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it
+ * successfully received a SMS. We can now safely mark it as
+ * transmitted */
+
+ if (!sms) {
+ LOGP(DLSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n");
+ return gsm411_send_rp_error(trans, rph->msg_ref,
+ GSM411_RP_CAUSE_PROTOCOL_ERR);
+ }
+
+ /* mark this SMS as sent in database */
+ db_sms_mark_delivered(sms);
+
+ send_signal(S_SMS_DELIVERED, trans, sms, 0);
+
+ sms_free(sms);
+ trans->sms.sms = NULL;
+
+ return 0;
+}
+
+static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans,
+ struct gsm411_rp_hdr *rph)
+{
+ struct gsm_network *net = trans->conn->network;
+ struct gsm_sms *sms = trans->sms.sms;
+ uint8_t cause_len = rph->data[0];
+ uint8_t cause = rph->data[1];
+
+ /* Error in response to MT RP_DATA, i.e. the MS did not
+ * successfully receive the SMS. We need to investigate
+ * the cause and take action depending on it */
+
+ LOGP(DLSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
+ subscr_name(trans->conn->subscr), cause_len, cause,
+ get_value_string(gsm411_rp_cause_strs, cause));
+
+ if (!sms) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "RX RP-ERR, but no sms in transaction?!?\n");
+ return -EINVAL;
+#if 0
+ return gsm411_send_rp_error(trans, rph->msg_ref,
+ GSM411_RP_CAUSE_PROTOCOL_ERR);
+#endif
+ }
+
+ if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) {
+ /* MS has not enough memory to store the message. We need
+ * to store this in our database and wait for a SMMA message */
+ /* FIXME */
+ send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM]);
+ } else {
+ send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER]);
+ }
+
+ sms_free(sms);
+ trans->sms.sms = NULL;
+
+ return 0;
+}
+
+static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans,
+ struct gsm411_rp_hdr *rph)
+{
+ int rc;
+
+ rc = gsm411_send_rp_ack(trans, rph->msg_ref);
+
+ /* MS tells us that it has memory for more SMS, we need
+ * to check if we have any pending messages for it and then
+ * transfer those */
+ send_signal(S_SMS_SMMA, trans, NULL, 0);
+
+ return rc;
+}
+
+/* receive RL DATA */
+static int gsm411_rx_rl_data(struct msgb *msg, struct gsm48_hdr *gh,
+ struct gsm_trans *trans)
+{
+ struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+ uint8_t msg_type = rp_data->msg_type & 0x07;
+ int rc = 0;
+
+ switch (msg_type) {
+ case GSM411_MT_RP_DATA_MO:
+ DEBUGP(DLSMS, "RX SMS RP-DATA (MO)\n");
+ rc = gsm411_rx_rp_data(msg, trans, rp_data);
+ break;
+ case GSM411_MT_RP_SMMA_MO:
+ DEBUGP(DLSMS, "RX SMS RP-SMMA\n");
+ rc = gsm411_rx_rp_smma(msg, trans, rp_data);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+/* receive RL REPORT */
+static int gsm411_rx_rl_report(struct msgb *msg, struct gsm48_hdr *gh,
+ struct gsm_trans *trans)
+{
+ struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+ uint8_t msg_type = rp_data->msg_type & 0x07;
+ int rc = 0;
+
+ switch (msg_type) {
+ case GSM411_MT_RP_ACK_MO:
+ DEBUGP(DLSMS, "RX SMS RP-ACK (MO)\n");
+ rc = gsm411_rx_rp_ack(msg, trans, rp_data);
+ break;
+ case GSM411_MT_RP_ERROR_MO:
+ DEBUGP(DLSMS, "RX SMS RP-ERROR (MO)\n");
+ rc = gsm411_rx_rp_error(msg, trans, rp_data);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+/* receive SM-RL sap message from SMR
+ * NOTE: Message is freed by sender
+ */
+int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type,
+ struct msgb *msg)
+{
+ struct gsm_trans *trans =
+ container_of(inst, struct gsm_trans, sms.smr_inst);
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int rc = 0;
+
+ switch (msg_type) {
+ case GSM411_SM_RL_DATA_IND:
+ rc = gsm411_rx_rl_data(msg, gh, trans);
+ break;
+ case GSM411_SM_RL_REPORT_IND:
+ if (gh)
+ rc = gsm411_rx_rl_report(msg, gh, trans);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Unhandled SM-RL message 0x%x\n", msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* receive MNCCSMS sap message from SMC
+ * NOTE: Message is freed by sender
+ */
+static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type,
+ struct msgb *msg)
+{
+ struct gsm_trans *trans =
+ container_of(inst, struct gsm_trans, sms.smc_inst);
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int rc = 0;
+
+ switch (msg_type) {
+ case GSM411_MNSMS_EST_IND:
+ case GSM411_MNSMS_DATA_IND:
+ DEBUGP(DLSMS, "MNSMS-DATA/EST-IND\n");
+ rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg);
+ break;
+ case GSM411_MNSMS_ERROR_IND:
+ if (gh)
+ DEBUGP(DLSMS, "MNSMS-ERROR-IND, cause %d (%s)\n",
+ gh->data[0],
+ get_value_string(gsm411_cp_cause_strs,
+ gh->data[0]));
+ else
+ DEBUGP(DLSMS, "MNSMS-ERROR-IND, no cause\n");
+ rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_NOTICE, "Unhandled MNCCSMS msg 0x%x\n", msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
+int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t msg_type = gh->msg_type;
+ uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
+ struct gsm_trans *trans;
+ int new_trans = 0;
+ int rc = 0;
+
+ if (!conn->subscr)
+ return -EIO;
+ /* FIXME: send some error message */
+
+ DEBUGP(DLSMS, "receiving data (trans_id=%x)\n", transaction_id);
+ trans = trans_find_by_id(conn, GSM48_PDISC_SMS, transaction_id);
+
+ /*
+ * A transaction we created but don't know about?
+ */
+ if (!trans && (transaction_id & 0x8) == 0) {
+ LOGP(DLSMS, LOGL_ERROR, "trans_id=%x allocated by us but known "
+ "to us anymore. We are ignoring it, maybe a CP-ERROR "
+ "from a MS?\n",
+ transaction_id);
+ return -EINVAL;
+ }
+
+ if (!trans) {
+ DEBUGP(DLSMS, " -> (new transaction)\n");
+ trans = trans_alloc(conn->network, conn->subscr,
+ GSM48_PDISC_SMS,
+ transaction_id, new_callref++);
+ if (!trans) {
+ DEBUGP(DLSMS, " -> No memory for trans\n");
+ /* FIXME: send some error message */
+ return -ENOMEM;
+ }
+ gsm411_smc_init(&trans->sms.smc_inst, 0, 1,
+ gsm411_mn_recv, gsm411_mm_send);
+ gsm411_smr_init(&trans->sms.smr_inst, 0, 1,
+ gsm411_rl_recv, gsm411_mn_send);
+
+ trans->conn = conn;
+
+ new_trans = 1;
+ }
+
+ /* 5.4: For MO, if a CP-DATA is received for a new
+ * transaction, equals reception of an implicit
+ * last CP-ACK for previous transaction */
+ if (trans->sms.smc_inst.cp_state == GSM411_CPS_IDLE
+ && msg_type == GSM411_MT_CP_DATA) {
+ int i;
+ struct gsm_trans *ptrans;
+
+ /* Scan through all remote initiated transactions */
+ for (i=8; i<15; i++) {
+ if (i == transaction_id)
+ continue;
+
+ ptrans = trans_find_by_id(conn, GSM48_PDISC_SMS, i);
+ if (!ptrans)
+ continue;
+
+ DEBUGP(DLSMS, "Implicit CP-ACK for trans_id=%x\n", i);
+
+ /* Finish it for good */
+ trans_free(ptrans);
+ }
+ }
+
+ gsm411_smc_recv(&trans->sms.smc_inst,
+ (new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND,
+ msg, msg_type);
+
+ return rc;
+}
+
+/* Take a SMS in gsm_sms structure and send it through an already
+ * existing lchan. We also assume that the caller ensured this lchan already
+ * has a SAPI3 RLL connection! */
+int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms)
+{
+ struct msgb *msg = gsm411_msgb_alloc();
+ struct gsm_trans *trans;
+ uint8_t *data, *rp_ud_len;
+ uint8_t msg_ref = sms_next_rp_msg_ref(&conn->next_rp_ref);
+ int transaction_id;
+ int rc;
+
+ transaction_id =
+ trans_assign_trans_id(conn->network, conn->subscr,
+ GSM48_PDISC_SMS, 0);
+ if (transaction_id == -1) {
+ LOGP(DLSMS, LOGL_ERROR, "No available transaction ids\n");
+ send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+ sms_free(sms);
+ msgb_free(msg);
+ return -EBUSY;
+ }
+
+ DEBUGP(DLSMS, "%s()\n", __func__);
+
+ /* FIXME: allocate transaction with message reference */
+ trans = trans_alloc(conn->network, conn->subscr,
+ GSM48_PDISC_SMS,
+ transaction_id, new_callref++);
+ if (!trans) {
+ LOGP(DLSMS, LOGL_ERROR, "No memory for trans\n");
+ send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+ sms_free(sms);
+ msgb_free(msg);
+ /* FIXME: send some error message */
+ return -ENOMEM;
+ }
+ gsm411_smc_init(&trans->sms.smc_inst, sms->id, 1,
+ gsm411_mn_recv, gsm411_mm_send);
+ gsm411_smr_init(&trans->sms.smr_inst, sms->id, 1,
+ gsm411_rl_recv, gsm411_mn_send);
+ trans->sms.sms = sms;
+
+ trans->conn = conn;
+
+ /* Hardcode SMSC Originating Address for now */
+ data = (uint8_t *)msgb_put(msg, 8);
+ data[0] = 0x07; /* originator length == 7 */
+ data[1] = 0x91; /* type of number: international, ISDN */
+ data[2] = 0x44; /* 447785016005 */
+ data[3] = 0x77;
+ data[4] = 0x58;
+ data[5] = 0x10;
+ data[6] = 0x06;
+ data[7] = 0x50;
+
+ /* Hardcoded Destination Address */
+ data = (uint8_t *)msgb_put(msg, 1);
+ data[0] = 0; /* destination length == 0 */
+
+ /* obtain a pointer for the rp_ud_len, so we can fill it later */
+ rp_ud_len = (uint8_t *)msgb_put(msg, 1);
+
+ /* generate the 03.40 SMS-DELIVER TPDU */
+ rc = gsm340_gen_sms_deliver_tpdu(msg, sms);
+ if (rc < 0) {
+ send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+ sms_free(sms);
+ trans->sms.sms = NULL;
+ trans_free(trans);
+ msgb_free(msg);
+ return rc;
+ }
+
+ *rp_ud_len = rc;
+
+ DEBUGP(DLSMS, "TX: SMS DELIVER\n");
+
+ rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED]);
+ db_sms_inc_deliver_attempts(trans->sms.sms);
+
+ return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg,
+ GSM411_MT_RP_DATA_MT, msg_ref, GSM411_SM_RL_DATA_REQ);
+}
+
+/* paging callback. Here we get called if paging a subscriber has
+ * succeeded or failed. */
+static int paging_cb_send_sms(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *_conn, void *_sms)
+{
+ struct gsm_subscriber_connection *conn = _conn;
+ struct gsm_sms *sms = _sms;
+ int rc = 0;
+
+ DEBUGP(DLSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p,"
+ "conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id);
+
+ if (hooknum != GSM_HOOK_RR_PAGING)
+ return -EINVAL;
+
+ switch (event) {
+ case GSM_PAGING_SUCCEEDED:
+ gsm411_send_sms(conn, sms);
+ break;
+ case GSM_PAGING_EXPIRED:
+ case GSM_PAGING_OOM:
+ case GSM_PAGING_BUSY:
+ send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event);
+ sms_free(sms);
+ rc = -ETIMEDOUT;
+ break;
+ default:
+ LOGP(DLSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event);
+ }
+
+ return rc;
+}
+
+/* high-level function to send a SMS to a given subscriber. The function
+ * will take care of paging the subscriber, establishing the RLL SAPI3
+ * connection, etc. */
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+ struct gsm_sms *sms)
+{
+ struct gsm_subscriber_connection *conn;
+ void *res;
+
+ /* check if we already have an open lchan to the subscriber.
+ * if yes, send the SMS this way */
+ conn = connection_for_subscr(subscr);
+ if (conn) {
+ LOGP(DLSMS, LOGL_DEBUG, "Sending SMS via already open connection %p to %s\n",
+ conn, subscr_name(subscr));
+ return gsm411_send_sms(conn, sms);
+ }
+
+ /* if not, we have to start paging */
+ LOGP(DLSMS, LOGL_DEBUG, "Sending SMS: no connection open, start paging %s\n",
+ subscr_name(subscr));
+ res = subscr_request_channel(subscr, RSL_CHANNEED_SDCCH,
+ paging_cb_send_sms, sms);
+ if (!res) {
+ send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, GSM_PAGING_BUSY);
+ sms_free(sms);
+ }
+ return 0;
+}
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans)
+{
+ /* cleanup SMS instance */
+ gsm411_smr_clear(&trans->sms.smr_inst);
+ trans->sms.smr_inst.rl_recv = NULL;
+ trans->sms.smr_inst.mn_send = NULL;
+
+ gsm411_smc_clear(&trans->sms.smc_inst);
+ trans->sms.smc_inst.mn_recv = NULL;
+ trans->sms.smc_inst.mm_send = NULL;
+
+ if (trans->sms.sms) {
+ LOGP(DLSMS, LOGL_ERROR, "Transaction contains SMS.\n");
+ send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0);
+ sms_free(trans->sms.sms);
+ trans->sms.sms = NULL;
+ }
+}
+
+void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
+{
+ struct gsm_network *net;
+ struct gsm_trans *trans, *tmp;
+
+ net = conn->network;
+
+ llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry) {
+ struct gsm_sms *sms;
+
+ if (trans->conn != conn)
+ continue;
+ if (trans->protocol != GSM48_PDISC_SMS)
+ continue;
+
+ sms = trans->sms.sms;
+ if (!sms) {
+ LOGP(DLSMS, LOGL_ERROR, "SAPI Reject but no SMS.\n");
+ continue;
+ }
+
+ send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+ sms_free(sms);
+ trans->sms.sms = NULL;
+ trans_free(trans);
+ }
+}
+
diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c
new file mode 100644
index 000000000..479d6fbd2
--- /dev/null
+++ b/src/libmsc/gsm_04_80.c
@@ -0,0 +1,155 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/bsc_api.h>
+
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+
+static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag)
+{
+ uint8_t *data = msgb_push(msgb, 2);
+
+ data[0] = tag;
+ data[1] = msgb->len - 2;
+ return data;
+}
+
+static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag,
+ uint8_t value)
+{
+ uint8_t *data = msgb_push(msgb, 3);
+
+ data[0] = tag;
+ data[1] = 1;
+ data[2] = value;
+ return data;
+}
+
+
+/* Send response to a mobile-originated ProcessUnstructuredSS-Request */
+int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
+ const struct msgb *in_msg, const char *response_text,
+ const struct ss_request *req)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD RSP");
+ struct gsm48_hdr *gh;
+ uint8_t *ptr8;
+ int response_len;
+
+ /* First put the payload text into the message */
+ ptr8 = msgb_put(msg, 0);
+ gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len);
+ msgb_put(msg, response_len);
+
+ /* Then wrap it as an Octet String */
+ msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG);
+
+ /* Pre-pend the DCS octet string */
+ msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
+
+ /* Then wrap these as a Sequence */
+ msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+ /* Pre-pend the operation code */
+ msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
+ GSM0480_OP_CODE_PROCESS_USS_REQ);
+
+ /* Wrap the operation code and IA5 string as a sequence */
+ msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+ /* Pre-pend the invoke ID */
+ msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+ /* Wrap this up as a Return Result component */
+ msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+
+ /* Wrap the component in a Facility message */
+ msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+ /* And finally pre-pend the L3 header */
+ gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id
+ | (1<<7); /* TI direction = 1 */
+ gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
+ const struct msgb *in_msg,
+ const struct ss_request *req)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD REJ");
+ struct gsm48_hdr *gh;
+
+ /* First insert the problem code */
+ msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL,
+ GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
+
+ /* Before it insert the invoke ID */
+ msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+ /* Wrap this up as a Reject component */
+ msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT);
+
+ /* Wrap the component in a Facility message */
+ msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+ /* And finally pre-pend the L3 header */
+ gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_NC_SS;
+ gh->proto_discr |= req->transaction_id | (1<<7); /* TI direction = 1 */
+ gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text)
+{
+ struct msgb *msg = gsm0480_create_ussd_notify(level, text);
+ if (!msg)
+ return -1;
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg = gsm0480_create_ussd_release_complete();
+ if (!msg)
+ return -1;
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c
new file mode 100644
index 000000000..1a03cf76e
--- /dev/null
+++ b/src/libmsc/gsm_subscriber.c
@@ -0,0 +1,422 @@
+/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+#include <stdbool.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <openbsc/chan_alloc.h>
+
+void *tall_sub_req_ctx;
+
+extern struct llist_head *subscr_bsc_active_subscribers(void);
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+ gsm_cbfn *cb, void *cb_data);
+
+
+/*
+ * Struct for pending channel requests. This is managed in the
+ * llist_head requests of each subscriber. The reference counting
+ * should work in such a way that a subscriber with a pending request
+ * remains in memory.
+ */
+struct subscr_request {
+ struct llist_head entry;
+
+ /* the callback data */
+ gsm_cbfn *cbfn;
+ void *param;
+};
+
+static struct gsm_subscriber *get_subscriber(struct gsm_subscriber_group *sgrp,
+ int type, const char *ident)
+{
+ struct gsm_subscriber *subscr = db_get_subscriber(type, ident);
+ if (subscr)
+ subscr->group = sgrp;
+ return subscr;
+}
+
+/*
+ * We got the channel assigned and can now hand this channel
+ * over to one of our callbacks.
+ */
+static int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *data, void *param)
+{
+ struct subscr_request *request, *tmp;
+ struct gsm_subscriber_connection *conn = data;
+ struct gsm_subscriber *subscr = param;
+ struct paging_signal_data sig_data;
+ struct bsc_subscr *bsub;
+ struct gsm_network *net;
+
+ OSMO_ASSERT(subscr && subscr->is_paging);
+ net = subscr->group->net;
+
+ /*
+ * Stop paging on all other BTS. E.g. if this is
+ * the first timeout on a BTS then the others will
+ * timeout soon as well. Let's just stop everything
+ * and forget we wanted to page.
+ */
+
+ /* TODO MSC split -- creating a BSC subscriber directly from MSC data
+ * structures in RAM. At some point the MSC will send a message to the
+ * BSC instead. */
+ bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers,
+ subscr->imsi);
+ bsub->tmsi = subscr->tmsi;
+ bsub->lac = subscr->lac;
+ paging_request_stop(&net->bts_list, NULL, bsub, NULL, NULL);
+ bsc_subscr_put(bsub);
+
+ /* Inform parts of the system we don't know */
+ sig_data.subscr = subscr;
+ sig_data.bts = conn ? conn->bts : NULL;
+ sig_data.conn = conn;
+ sig_data.paging_result = event;
+ osmo_signal_dispatch(
+ SS_PAGING,
+ event == GSM_PAGING_SUCCEEDED ?
+ S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
+ &sig_data
+ );
+
+ llist_for_each_entry_safe(request, tmp, &subscr->requests, entry) {
+ llist_del(&request->entry);
+ request->cbfn(hooknum, event, msg, data, request->param);
+ talloc_free(request);
+ }
+
+ /* balanced with the moment we start paging */
+ subscr->is_paging = 0;
+ subscr_put(subscr);
+ return 0;
+}
+
+static int subscr_paging_sec_cb(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *data, void *param)
+{
+ int rc;
+
+ switch (event) {
+ case GSM_SECURITY_AUTH_FAILED:
+ /* Dispatch as paging failure */
+ rc = subscr_paging_dispatch(
+ GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED,
+ msg, data, param);
+ break;
+
+ case GSM_SECURITY_NOAVAIL:
+ case GSM_SECURITY_SUCCEEDED:
+ /* Dispatch as paging failure */
+ rc = subscr_paging_dispatch(
+ GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+ msg, data, param);
+ break;
+
+ default:
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int subscr_paging_cb(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *data, void *param)
+{
+ struct gsm_subscriber_connection *conn = data;
+ struct gsm48_hdr *gh;
+ struct gsm48_pag_resp *pr;
+
+ /* Other cases mean problem, dispatch direclty */
+ if (event != GSM_PAGING_SUCCEEDED)
+ return subscr_paging_dispatch(hooknum, event, msg, data, param);
+
+ /* Get paging response */
+ gh = msgb_l3(msg);
+ pr = (struct gsm48_pag_resp *)gh->data;
+
+ /* We _really_ have a channel, secure it now ! */
+ return gsm48_secure_channel(conn, pr->key_seq, subscr_paging_sec_cb, param);
+}
+
+struct subscr_request *subscr_request_channel(struct gsm_subscriber *subscr,
+ int channel_type, gsm_cbfn *cbfn, void *param)
+{
+ int rc;
+ struct subscr_request *request;
+ struct bsc_subscr *bsub;
+ struct gsm_network *net = subscr->group->net;
+
+ /* Start paging.. we know it is async so we can do it before */
+ if (!subscr->is_paging) {
+ LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet.\n",
+ subscr_name(subscr));
+ /* TODO MSC split -- creating a BSC subscriber directly from
+ * MSC data structures in RAM. At some point the MSC will send
+ * a message to the BSC instead. */
+ bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers,
+ subscr->imsi);
+ bsub->tmsi = subscr->tmsi;
+ bsub->lac = subscr->lac;
+ rc = paging_request(net, bsub, channel_type, subscr_paging_cb,
+ subscr);
+ bsc_subscr_put(bsub);
+ if (rc <= 0) {
+ LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n",
+ subscr_name(subscr), rc);
+ return NULL;
+ }
+ /* reduced on the first paging callback */
+ subscr_get(subscr);
+ subscr->is_paging = 1;
+ }
+
+ /* TODO: Stop paging in case of memory allocation failure */
+ request = talloc_zero(subscr, struct subscr_request);
+ if (!request)
+ return NULL;
+
+ request->cbfn = cbfn;
+ request->param = param;
+ llist_add_tail(&request->entry, &subscr->requests);
+ return request;
+}
+
+void subscr_remove_request(struct subscr_request *request)
+{
+ llist_del(&request->entry);
+ talloc_free(request);
+}
+
+struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp,
+ const char *imsi)
+{
+ struct gsm_subscriber *subscr = db_create_subscriber(imsi,
+ sgrp->net->ext_min,
+ sgrp->net->ext_max,
+ sgrp->net->auto_assign_exten);
+ if (subscr)
+ subscr->group = sgrp;
+ return subscr;
+}
+
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_subscriber_group *sgrp,
+ uint32_t tmsi)
+{
+ char tmsi_string[14];
+ struct gsm_subscriber *subscr;
+
+ /* we might have a record in memory already */
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (tmsi == subscr->tmsi)
+ return subscr_get(subscr);
+ }
+
+ sprintf(tmsi_string, "%u", tmsi);
+ return get_subscriber(sgrp, GSM_SUBSCRIBER_TMSI, tmsi_string);
+}
+
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_subscriber_group *sgrp,
+ const char *imsi)
+{
+ struct gsm_subscriber *subscr;
+
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (strcmp(subscr->imsi, imsi) == 0)
+ return subscr_get(subscr);
+ }
+
+ return get_subscriber(sgrp, GSM_SUBSCRIBER_IMSI, imsi);
+}
+
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_subscriber_group *sgrp,
+ const char *ext)
+{
+ struct gsm_subscriber *subscr;
+
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (strcmp(subscr->extension, ext) == 0)
+ return subscr_get(subscr);
+ }
+
+ return get_subscriber(sgrp, GSM_SUBSCRIBER_EXTENSION, ext);
+}
+
+struct gsm_subscriber *subscr_get_by_id(struct gsm_subscriber_group *sgrp,
+ unsigned long long id)
+{
+ struct gsm_subscriber *subscr;
+ char buf[32];
+ sprintf(buf, "%llu", id);
+
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (subscr->id == id)
+ return subscr_get(subscr);
+ }
+
+ return get_subscriber(sgrp, GSM_SUBSCRIBER_ID, buf);
+}
+
+int subscr_update_expire_lu(struct gsm_subscriber *s, struct gsm_bts *bts)
+{
+ int rc;
+
+ if (!s) {
+ LOGP(DMM, LOGL_ERROR, "LU Expiration but NULL subscriber\n");
+ return -1;
+ }
+ if (!bts) {
+ LOGP(DMM, LOGL_ERROR, "%s: LU Expiration but NULL bts\n",
+ subscr_name(s));
+ return -1;
+ }
+
+ /* Table 10.5.33: The T3212 timeout value field is coded as the
+ * binary representation of the timeout value for
+ * periodic updating in decihours. Mark the subscriber as
+ * inactive if it missed two consecutive location updates.
+ * Timeout is twice the t3212 value plus one minute */
+
+ /* Is expiration handling enabled? */
+ if (bts->si_common.chan_desc.t3212 == 0)
+ s->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION;
+ else
+ s->expire_lu = time(NULL) +
+ (bts->si_common.chan_desc.t3212 * 60 * 6 * 2) + 60;
+
+ rc = db_sync_subscriber(s);
+ db_subscriber_update(s);
+ return rc;
+}
+
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason)
+{
+ int rc;
+
+ /* FIXME: Migrate pending requests from one BSC to another */
+ switch (reason) {
+ case GSM_SUBSCRIBER_UPDATE_ATTACHED:
+ s->group = bts->network->subscr_group;
+ /* Indicate "attached to LAC" */
+ s->lac = bts->location_area_code;
+
+ LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n",
+ subscr_name(s), s->lac);
+
+ /*
+ * The below will set a new expire_lu but as a side-effect
+ * the new lac will be saved in the database.
+ */
+ rc = subscr_update_expire_lu(s, bts);
+ osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, s);
+ break;
+ case GSM_SUBSCRIBER_UPDATE_DETACHED:
+ /* Only detach if we are currently in this area */
+ if (bts->location_area_code == s->lac)
+ s->lac = GSM_LAC_RESERVED_DETACHED;
+ LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s));
+ rc = db_sync_subscriber(s);
+ db_subscriber_update(s);
+ osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_DETACHED, s);
+ break;
+ default:
+ fprintf(stderr, "subscr_update with unknown reason: %d\n",
+ reason);
+ rc = db_sync_subscriber(s);
+ db_subscriber_update(s);
+ break;
+ };
+
+ return rc;
+}
+
+void subscr_update_from_db(struct gsm_subscriber *sub)
+{
+ db_subscriber_update(sub);
+}
+
+static void subscr_expire_callback(void *data, long long unsigned int id)
+{
+ struct gsm_network *net = data;
+ struct gsm_subscriber *s = subscr_get_by_id(net->subscr_group, id);
+ struct gsm_subscriber_connection *conn = connection_for_subscr(s);
+
+ /*
+ * The subscriber is active and the phone stopped the timer. As
+ * we don't want to periodically update the database for active
+ * subscribers we will just do it when the subscriber was selected
+ * for expiration. This way on the next around another subscriber
+ * will be selected.
+ */
+ if (conn && conn->expire_timer_stopped) {
+ LOGP(DMM, LOGL_DEBUG, "Not expiring subscriber %s (ID %llu)\n",
+ subscr_name(s), id);
+ subscr_update_expire_lu(s, conn->bts);
+ subscr_put(s);
+ return;
+ }
+
+
+ LOGP(DMM, LOGL_NOTICE, "Expiring inactive subscriber %s (ID %llu)\n",
+ subscr_name(s), id);
+ s->lac = GSM_LAC_RESERVED_DETACHED;
+ db_sync_subscriber(s);
+
+ subscr_put(s);
+}
+
+void subscr_expire(struct gsm_subscriber_group *sgrp)
+{
+ db_subscriber_expire(sgrp->net, subscr_expire_callback);
+}
+
+struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr)
+{
+ /* FIXME: replace this with a backpointer in gsm_subscriber? */
+ struct gsm_network *net = subscr->group->net;
+ struct gsm_subscriber_connection *conn;
+
+ llist_for_each_entry(conn, &net->subscr_conns, entry) {
+ if (conn->subscr == subscr)
+ return conn;
+ }
+
+ return NULL;
+}
diff --git a/src/libmsc/meas_feed.c b/src/libmsc/meas_feed.c
new file mode 100644
index 000000000..3ddcdc39c
--- /dev/null
+++ b/src/libmsc/meas_feed.c
@@ -0,0 +1,167 @@
+/* UDP-Feed of measurement reports */
+
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/meas_feed.h>
+#include <openbsc/vty.h>
+
+#include "meas_feed.h"
+
+struct meas_feed_state {
+ struct osmo_wqueue wqueue;
+ char scenario[31+1];
+ char *dst_host;
+ uint16_t dst_port;
+};
+
+
+static struct meas_feed_state g_mfs;
+
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+ struct msgb *msg;
+ struct meas_feed_meas *mfm;
+ struct gsm_subscriber *subscr;
+
+ /* ignore measurements as long as we don't know who it is */
+ if (!mr->lchan || !mr->lchan->conn || !mr->lchan->conn->subscr)
+ return 0;
+
+ subscr = mr->lchan->conn->subscr;
+
+ msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed");
+ if (!msg)
+ return 0;
+
+ /* fill in the header */
+ mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm));
+ mfm->hdr.msg_type = MEAS_FEED_MEAS;
+ mfm->hdr.version = MEAS_FEED_VERSION;
+
+ /* fill in MEAS_FEED_MEAS specific header */
+ osmo_strlcpy(mfm->imsi, subscr->imsi, sizeof(mfm->imsi));
+ osmo_strlcpy(mfm->name, subscr->name, sizeof(mfm->name));
+ osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario));
+
+ /* copy the entire measurement report */
+ memcpy(&mfm->mr, mr, sizeof(mfm->mr));
+
+ /* copy channel information */
+ /* we assume that the measurement report always belong to some timeslot */
+ mfm->lchan_type = (uint8_t)mr->lchan->type;
+ mfm->pchan_type = (uint8_t)mr->lchan->ts->pchan;
+ mfm->bts_nr = mr->lchan->ts->trx->bts->nr;
+ mfm->trx_nr = mr->lchan->ts->trx->nr;
+ mfm->ts_nr = mr->lchan->ts->nr;
+ mfm->ss_nr = mr->lchan->nr;
+
+ /* and send it to the socket */
+ if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0)
+ msgb_free(msg);
+
+ return 0;
+}
+
+static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *sdata = signal_data;
+
+ if (subsys != SS_LCHAN)
+ return 0;
+
+ if (signal == S_LCHAN_MEAS_REP)
+ process_meas_rep(sdata->mr);
+
+ return 0;
+}
+
+static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ return write(ofd->fd, msgb_data(msg), msgb_length(msg));
+}
+
+static int feed_read_cb(struct osmo_fd *ofd)
+{
+ int rc;
+ char buf[256];
+
+ rc = read(ofd->fd, buf, sizeof(buf));
+ ofd->fd &= ~BSC_FD_READ;
+
+ return rc;
+}
+
+int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port)
+{
+ int rc;
+ int already_initialized = 0;
+
+ if (g_mfs.wqueue.bfd.fd)
+ already_initialized = 1;
+
+
+ if (already_initialized &&
+ !strcmp(dst_host, g_mfs.dst_host) &&
+ dst_port == g_mfs.dst_port)
+ return 0;
+
+ if (!already_initialized) {
+ osmo_wqueue_init(&g_mfs.wqueue, 10);
+ g_mfs.wqueue.write_cb = feed_write_cb;
+ g_mfs.wqueue.read_cb = feed_read_cb;
+ osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
+ }
+
+ if (already_initialized) {
+ osmo_wqueue_clear(&g_mfs.wqueue);
+ osmo_fd_unregister(&g_mfs.wqueue.bfd);
+ close(g_mfs.wqueue.bfd.fd);
+ /* don't set to zero, as that would mean 'not yet initialized' */
+ g_mfs.wqueue.bfd.fd = -1;
+ }
+ rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM,
+ IPPROTO_UDP, dst_host, dst_port,
+ OSMO_SOCK_F_CONNECT);
+ if (rc < 0)
+ return rc;
+
+ g_mfs.wqueue.bfd.when &= ~BSC_FD_READ;
+
+ if (g_mfs.dst_host)
+ talloc_free(g_mfs.dst_host);
+ g_mfs.dst_host = talloc_strdup(NULL, dst_host);
+ g_mfs.dst_port = dst_port;
+
+ return 0;
+}
+
+void meas_feed_cfg_get(char **host, uint16_t *port)
+{
+ *port = g_mfs.dst_port;
+ *host = g_mfs.dst_host;
+}
+
+void meas_feed_scenario_set(const char *name)
+{
+ osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario));
+}
+
+const char *meas_feed_scenario_get(void)
+{
+ return g_mfs.scenario;
+}
diff --git a/src/libmsc/meas_feed.h b/src/libmsc/meas_feed.h
new file mode 100644
index 000000000..782a9616c
--- /dev/null
+++ b/src/libmsc/meas_feed.h
@@ -0,0 +1,12 @@
+#ifndef _INT_MEAS_FEED_H
+#define _INT_MEAS_FEED_H
+
+#include <stdint.h>
+
+int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port);
+void meas_feed_cfg_get(char **host, uint16_t *port);
+
+void meas_feed_scenario_set(const char *name);
+const char *meas_feed_scenario_get(void);
+
+#endif /* _INT_MEAS_FEED_H */
diff --git a/src/libmsc/mncc.c b/src/libmsc/mncc.c
new file mode 100644
index 000000000..8110eadca
--- /dev/null
+++ b/src/libmsc/mncc.c
@@ -0,0 +1,107 @@
+/* mncc.c - utility routines for the MNCC API between the 04.08
+ * message parsing and the actual Call Control logic */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+
+static const struct value_string mncc_names[] = {
+ { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" },
+ { MNCC_SETUP_IND, "MNCC_SETUP_IND" },
+ { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" },
+ { MNCC_SETUP_CNF, "MNCC_SETUP_CNF" },
+ { MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" },
+ { MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" },
+ { MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" },
+ { MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" },
+ { MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" },
+ { MNCC_ALERT_REQ, "MNCC_ALERT_REQ" },
+ { MNCC_ALERT_IND, "MNCC_ALERT_IND" },
+ { MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" },
+ { MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" },
+ { MNCC_DISC_REQ, "MNCC_DISC_REQ" },
+ { MNCC_DISC_IND, "MNCC_DISC_IND" },
+ { MNCC_REL_REQ, "MNCC_REL_REQ" },
+ { MNCC_REL_IND, "MNCC_REL_IND" },
+ { MNCC_REL_CNF, "MNCC_REL_CNF" },
+ { MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" },
+ { MNCC_FACILITY_IND, "MNCC_FACILITY_IND" },
+ { MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" },
+ { MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" },
+ { MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" },
+ { MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" },
+ { MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" },
+ { MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" },
+ { MNCC_MODIFY_IND, "MNCC_MODIFY_IND" },
+ { MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" },
+ { MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" },
+ { MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" },
+ { MNCC_HOLD_IND, "MNCC_HOLD_IND" },
+ { MNCC_HOLD_CNF, "MNCC_HOLD_CNF" },
+ { MNCC_HOLD_REJ, "MNCC_HOLD_REJ" },
+ { MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" },
+ { MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" },
+ { MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" },
+ { MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" },
+ { MNCC_USERINFO_IND, "MNCC_USERINFO_IND" },
+ { MNCC_REJ_REQ, "MNCC_REJ_REQ" },
+ { MNCC_REJ_IND, "MNCC_REJ_IND" },
+ { MNCC_BRIDGE, "MNCC_BRIDGE" },
+ { MNCC_FRAME_RECV, "MNCC_FRAME_RECV" },
+ { MNCC_FRAME_DROP, "MNCC_FRAME_DROP" },
+ { MNCC_LCHAN_MODIFY, "MNCC_LCHAN_MODIFY" },
+ { MNCC_RTP_CREATE, "MNCC_RTP_CREATE" },
+ { MNCC_RTP_CONNECT, "MNCC_RTP_CONNECT" },
+ { MNCC_RTP_FREE, "MNCC_RTP_FREE" },
+ { GSM_TCHF_FRAME, "GSM_TCHF_FRAME" },
+ { GSM_TCHF_FRAME_EFR, "GSM_TCHF_FRAME_EFR" },
+ { GSM_TCHH_FRAME, "GSM_TCHH_FRAME" },
+ { GSM_TCH_FRAME_AMR, "GSM_TCH_FRAME_AMR" },
+ { GSM_BAD_FRAME, "GSM_BAD_FRAME" },
+ { 0, NULL },
+};
+
+const char *get_mncc_name(int value)
+{
+ return get_value_string(mncc_names, value);
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+ data->fields |= MNCC_F_CAUSE;
+ data->cause.location = loc;
+ data->cause.value = val;
+}
+
diff --git a/src/libmsc/mncc_builtin.c b/src/libmsc/mncc_builtin.c
new file mode 100644
index 000000000..067cc92f8
--- /dev/null
+++ b/src/libmsc/mncc_builtin.c
@@ -0,0 +1,426 @@
+/* mncc_builtin.c - default, minimal built-in MNCC Application for
+ * standalone bsc_hack (network-in-the-box mode) */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/mncc_int.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+void *tall_call_ctx;
+
+static LLIST_HEAD(call_list);
+
+static uint32_t new_callref = 0x00000001;
+
+struct mncc_int mncc_int = {
+ .def_codec = { GSM48_CMODE_SPEECH_V1, GSM48_CMODE_SPEECH_V1 },
+};
+
+static void free_call(struct gsm_call *call)
+{
+ llist_del(&call->entry);
+ DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
+ talloc_free(call);
+}
+
+
+static struct gsm_call *get_call_ref(uint32_t callref)
+{
+ struct gsm_call *callt;
+
+ llist_for_each_entry(callt, &call_list, entry) {
+ if (callt->callref == callref)
+ return callt;
+ }
+ return NULL;
+}
+
+uint8_t mncc_codec_for_mode(int lchan_type)
+{
+ /* FIXME: check codec capabilities of the phone */
+
+ if (lchan_type != GSM_LCHAN_TCH_H)
+ return mncc_int.def_codec[0];
+ else
+ return mncc_int.def_codec[1];
+}
+
+static uint8_t determine_lchan_mode(struct gsm_mncc *setup)
+{
+ return mncc_codec_for_mode(setup->lchan_type);
+}
+
+/* on incoming call, look up database and send setup to remote subscr. */
+static int mncc_setup_ind(struct gsm_call *call, int msg_type,
+ struct gsm_mncc *setup)
+{
+ struct gsm_mncc mncc;
+ struct gsm_call *remote;
+
+ memset(&mncc, 0, sizeof(struct gsm_mncc));
+ mncc.callref = call->callref;
+
+ /* already have remote call */
+ if (call->remote_ref)
+ return 0;
+
+ /* transfer mode 1 would be packet mode, which was never specified */
+ if (setup->bearer_cap.mode != 0) {
+ LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support "
+ "packet mode\n", call->callref);
+ mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+ goto out_reject;
+ }
+
+ /* we currently only do speech */
+ if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) {
+ LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support "
+ "voice calls\n", call->callref);
+ mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+ goto out_reject;
+ }
+
+ /* create remote call */
+ if (!(remote = talloc_zero(tall_call_ctx, struct gsm_call))) {
+ mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ goto out_reject;
+ }
+ llist_add_tail(&remote->entry, &call_list);
+ remote->net = call->net;
+ remote->callref = new_callref++;
+ DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n",
+ call->callref, remote->callref);
+
+ /* link remote call */
+ call->remote_ref = remote->callref;
+ remote->remote_ref = call->callref;
+
+ /* send call proceeding */
+ memset(&mncc, 0, sizeof(struct gsm_mncc));
+ mncc.callref = call->callref;
+ DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref);
+ mncc_tx_to_cc(call->net, MNCC_CALL_PROC_REQ, &mncc);
+
+ /* modify mode */
+ memset(&mncc, 0, sizeof(struct gsm_mncc));
+ mncc.callref = call->callref;
+ mncc.lchan_mode = determine_lchan_mode(setup);
+ DEBUGP(DMNCC, "(call %x) Modify channel mode: %s\n", call->callref,
+ get_value_string(gsm48_chan_mode_names, mncc.lchan_mode));
+ mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, &mncc);
+
+ /* send setup to remote */
+// setup->fields |= MNCC_F_SIGNAL;
+// setup->signal = GSM48_SIGNAL_DIALTONE;
+ setup->callref = remote->callref;
+ DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref);
+ return mncc_tx_to_cc(remote->net, MNCC_SETUP_REQ, setup);
+
+out_reject:
+ mncc_tx_to_cc(call->net, MNCC_REJ_REQ, &mncc);
+ free_call(call);
+ return 0;
+}
+
+static int mncc_alert_ind(struct gsm_call *call, int msg_type,
+ struct gsm_mncc *alert)
+{
+ struct gsm_call *remote;
+
+ /* send alerting to remote */
+ if (!(remote = get_call_ref(call->remote_ref)))
+ return 0;
+ alert->callref = remote->callref;
+ DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref);
+ return mncc_tx_to_cc(remote->net, MNCC_ALERT_REQ, alert);
+}
+
+static int mncc_notify_ind(struct gsm_call *call, int msg_type,
+ struct gsm_mncc *notify)
+{
+ struct gsm_call *remote;
+
+ /* send notify to remote */
+ if (!(remote = get_call_ref(call->remote_ref)))
+ return 0;
+ notify->callref = remote->callref;
+ DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref);
+ return mncc_tx_to_cc(remote->net, MNCC_NOTIFY_REQ, notify);
+}
+
+static int mncc_setup_cnf(struct gsm_call *call, int msg_type,
+ struct gsm_mncc *connect)
+{
+ struct gsm_mncc connect_ack, frame_recv;
+ struct gsm_network *net = call->net;
+ struct gsm_call *remote;
+ struct gsm_mncc_bridge bridge = { .msg_type = MNCC_BRIDGE };
+
+ /* acknowledge connect */
+ memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+ connect_ack.callref = call->callref;
+ DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref);
+ mncc_tx_to_cc(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack);
+
+ /* send connect message to remote */
+ if (!(remote = get_call_ref(call->remote_ref)))
+ return 0;
+ connect->callref = remote->callref;
+ DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref);
+ mncc_tx_to_cc(remote->net, MNCC_SETUP_RSP, connect);
+
+ /* bridge tch */
+ bridge.callref[0] = call->callref;
+ bridge.callref[1] = call->remote_ref;
+ DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref);
+
+ /* in direct mode, we always have to bridge the channels */
+ if (ipacc_rtp_direct)
+ return mncc_tx_to_cc(call->net, MNCC_BRIDGE, &bridge);
+
+ /* proxy mode */
+ if (!net->handover.active) {
+ /* in the no-handover case, we can bridge, i.e. use
+ * the old RTP proxy code */
+ return mncc_tx_to_cc(call->net, MNCC_BRIDGE, &bridge);
+ } else {
+ /* in case of handover, we need to re-write the RTP
+ * SSRC, sequence and timestamp values and thus
+ * need to enable RTP receive for both directions */
+ memset(&frame_recv, 0, sizeof(struct gsm_mncc));
+ frame_recv.callref = call->callref;
+ mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+ frame_recv.callref = call->remote_ref;
+ return mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+ }
+}
+
+static int mncc_disc_ind(struct gsm_call *call, int msg_type,
+ struct gsm_mncc *disc)
+{
+ struct gsm_call *remote;
+
+ /* send release */
+ DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n",
+ call->callref, disc->cause.value);
+ mncc_tx_to_cc(call->net, MNCC_REL_REQ, disc);
+
+ /* send disc to remote */
+ if (!(remote = get_call_ref(call->remote_ref))) {
+ return 0;
+ }
+ disc->callref = remote->callref;
+ DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n",
+ remote->callref, disc->cause.value);
+ return mncc_tx_to_cc(remote->net, MNCC_DISC_REQ, disc);
+}
+
+static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+ struct gsm_call *remote;
+
+ /* send release to remote */
+ if (!(remote = get_call_ref(call->remote_ref))) {
+ free_call(call);
+ return 0;
+ }
+
+ rel->callref = remote->callref;
+ DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n",
+ call->callref, rel->cause.value);
+
+ /*
+ * Release this side of the call right now. Otherwise we end up
+ * in this method for the other call and will also try to release
+ * it and then we will end up with a double free and a crash
+ */
+ free_call(call);
+ mncc_tx_to_cc(remote->net, MNCC_REL_REQ, rel);
+
+ return 0;
+}
+
+static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+ free_call(call);
+ return 0;
+}
+
+/* receiving a (speech) traffic frame from the BSC code */
+static int mncc_rcv_data(struct gsm_call *call, int msg_type,
+ struct gsm_data_frame *dfr)
+{
+ struct gsm_trans *remote_trans;
+
+ remote_trans = trans_find_by_callref(call->net, call->remote_ref);
+
+ /* this shouldn't really happen */
+ if (!remote_trans || !remote_trans->conn) {
+ LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n");
+ return -EIO;
+ }
+
+ /* RTP socket of remote end has meanwhile died */
+ if (!remote_trans->conn->lchan->abis_ip.rtp_socket)
+ return -EIO;
+
+ return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr);
+}
+
+
+/* Internal MNCC handler input function (from CC -> MNCC -> here) */
+int int_mncc_recv(struct gsm_network *net, struct msgb *msg)
+{
+ void *arg = msgb_data(msg);
+ struct gsm_mncc *data = arg;
+ int msg_type = data->msg_type;
+ int callref;
+ struct gsm_call *call = NULL, *callt;
+ int rc = 0;
+
+ /* Special messages */
+ switch(msg_type) {
+ }
+
+ /* find callref */
+ callref = data->callref;
+ llist_for_each_entry(callt, &call_list, entry) {
+ if (callt->callref == callref) {
+ call = callt;
+ break;
+ }
+ }
+
+ /* create callref, if setup is received */
+ if (!call) {
+ if (msg_type != MNCC_SETUP_IND)
+ goto out_free; /* drop */
+ /* create call */
+ if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
+ struct gsm_mncc rel;
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = callref;
+ mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ mncc_tx_to_cc(net, MNCC_REL_REQ, &rel);
+ goto out_free;
+ }
+ llist_add_tail(&call->entry, &call_list);
+ call->net = net;
+ call->callref = callref;
+ DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref);
+ }
+
+ if (mncc_is_data_frame(msg_type)) {
+ rc = mncc_rcv_data(call, msg_type, arg);
+ goto out_free;
+ }
+
+ DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref,
+ get_mncc_name(msg_type));
+
+ switch(msg_type) {
+ case MNCC_SETUP_IND:
+ rc = mncc_setup_ind(call, msg_type, arg);
+ break;
+ case MNCC_SETUP_CNF:
+ rc = mncc_setup_cnf(call, msg_type, arg);
+ break;
+ case MNCC_SETUP_COMPL_IND:
+ break;
+ case MNCC_CALL_CONF_IND:
+ /* we now need to MODIFY the channel */
+ data->lchan_mode = determine_lchan_mode(data);
+ mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, data);
+ break;
+ case MNCC_ALERT_IND:
+ rc = mncc_alert_ind(call, msg_type, arg);
+ break;
+ case MNCC_NOTIFY_IND:
+ rc = mncc_notify_ind(call, msg_type, arg);
+ break;
+ case MNCC_DISC_IND:
+ rc = mncc_disc_ind(call, msg_type, arg);
+ break;
+ case MNCC_REL_IND:
+ case MNCC_REJ_IND:
+ rc = mncc_rel_ind(call, msg_type, arg);
+ break;
+ case MNCC_REL_CNF:
+ rc = mncc_rel_cnf(call, msg_type, arg);
+ break;
+ case MNCC_FACILITY_IND:
+ break;
+ case MNCC_START_DTMF_IND:
+ rc = mncc_tx_to_cc(net, MNCC_START_DTMF_REJ, data);
+ break;
+ case MNCC_STOP_DTMF_IND:
+ rc = mncc_tx_to_cc(net, MNCC_STOP_DTMF_RSP, data);
+ break;
+ case MNCC_MODIFY_IND:
+ mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+ DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n",
+ call->callref, data->cause.value);
+ rc = mncc_tx_to_cc(net, MNCC_MODIFY_REJ, data);
+ break;
+ case MNCC_MODIFY_CNF:
+ break;
+ case MNCC_HOLD_IND:
+ mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+ DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n",
+ call->callref, data->cause.value);
+ rc = mncc_tx_to_cc(net, MNCC_HOLD_REJ, data);
+ break;
+ case MNCC_RETRIEVE_IND:
+ mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+ DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n",
+ call->callref, data->cause.value);
+ rc = mncc_tx_to_cc(net, MNCC_RETRIEVE_REJ, data);
+ break;
+ default:
+ LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref);
+ break;
+ }
+
+out_free:
+ msgb_free(msg);
+
+ return rc;
+}
diff --git a/src/libmsc/mncc_sock.c b/src/libmsc/mncc_sock.c
new file mode 100644
index 000000000..0efe3a156
--- /dev/null
+++ b/src/libmsc/mncc_sock.c
@@ -0,0 +1,318 @@
+/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * (C) 2012 by Holger Hans Peter Freyther
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+
+struct mncc_sock_state {
+ struct gsm_network *net;
+ struct osmo_fd listen_bfd; /* fd for listen socket */
+ struct osmo_fd conn_bfd; /* fd for connection to lcr */
+};
+
+/* input from CC code into mncc_sock */
+int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg)
+{
+ struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg);
+ int msg_type = mncc_in->msg_type;
+
+ /* Check if we currently have a MNCC handler connected */
+ if (net->mncc_state->conn_bfd.fd < 0) {
+ LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app "
+ "but socket is gone\n", get_mncc_name(msg_type));
+ if (!mncc_is_data_frame(msg_type)) {
+ /* release the request */
+ struct gsm_mncc mncc_out;
+ memset(&mncc_out, 0, sizeof(mncc_out));
+ mncc_out.callref = mncc_in->callref;
+ mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_TEMP_FAILURE);
+ mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out);
+ }
+ /* free the original message */
+ msgb_free(msg);
+ return -1;
+ }
+
+ /* FIXME: check for some maximum queue depth? */
+
+ /* Actually enqueue the message and mark socket write need */
+ msgb_enqueue(&net->upqueue, msg);
+ net->mncc_state->conn_bfd.when |= BSC_FD_WRITE;
+ return 0;
+}
+
+static void mncc_sock_close(struct mncc_sock_state *state)
+{
+ struct osmo_fd *bfd = &state->conn_bfd;
+
+ LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n");
+
+ close(bfd->fd);
+ bfd->fd = -1;
+ osmo_fd_unregister(bfd);
+
+ /* re-enable the generation of ACCEPT for new connections */
+ state->listen_bfd.when |= BSC_FD_READ;
+
+ /* release all exisitng calls */
+ gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC);
+
+ /* flush the queue */
+ while (!llist_empty(&state->net->upqueue)) {
+ struct msgb *msg = msgb_dequeue(&state->net->upqueue);
+ msgb_free(msg);
+ }
+}
+
+static int mncc_sock_read(struct osmo_fd *bfd)
+{
+ struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+ struct gsm_mncc *mncc_prim;
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx");
+ if (!msg)
+ return -ENOMEM;
+
+ mncc_prim = (struct gsm_mncc *) msg->tail;
+
+ rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+ if (rc == 0)
+ goto close;
+
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ goto close;
+ }
+
+ rc = mncc_tx_to_cc(state->net, mncc_prim->msg_type, mncc_prim);
+
+ /* as we always synchronously process the message in mncc_send() and
+ * its callbacks, we can free the message here. */
+ msgb_free(msg);
+
+ return rc;
+
+close:
+ msgb_free(msg);
+ mncc_sock_close(state);
+ return -1;
+}
+
+static int mncc_sock_write(struct osmo_fd *bfd)
+{
+ struct mncc_sock_state *state = bfd->data;
+ struct gsm_network *net = state->net;
+ int rc;
+
+ while (!llist_empty(&net->upqueue)) {
+ struct msgb *msg, *msg2;
+ struct gsm_mncc *mncc_prim;
+
+ /* peek at the beginning of the queue */
+ msg = llist_entry(net->upqueue.next, struct msgb, list);
+ mncc_prim = (struct gsm_mncc *)msg->data;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+ if (!msgb_length(msg)) {
+ LOGP(DMNCC, LOGL_ERROR, "message type (%d) with ZERO "
+ "bytes!\n", mncc_prim->msg_type);
+ goto dontsend;
+ }
+
+ /* try to send it over the socket */
+ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc == 0)
+ goto close;
+ if (rc < 0) {
+ if (errno == EAGAIN) {
+ bfd->when |= BSC_FD_WRITE;
+ break;
+ }
+ goto close;
+ }
+
+dontsend:
+ /* _after_ we send it, we can deueue */
+ msg2 = msgb_dequeue(&net->upqueue);
+ assert(msg == msg2);
+ msgb_free(msg);
+ }
+ return 0;
+
+close:
+ mncc_sock_close(state);
+
+ return -1;
+}
+
+static int mncc_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+ int rc = 0;
+
+ if (flags & BSC_FD_READ)
+ rc = mncc_sock_read(bfd);
+ if (rc < 0)
+ return rc;
+
+ if (flags & BSC_FD_WRITE)
+ rc = mncc_sock_write(bfd);
+
+ return rc;
+}
+
+/**
+ * Send a version indication to the remote.
+ */
+static void queue_hello(struct mncc_sock_state *mncc)
+{
+ struct gsm_mncc_hello *hello;
+ struct msgb *msg;
+
+ msg = msgb_alloc(512, "mncc hello");
+ if (!msg) {
+ LOGP(DMNCC, LOGL_ERROR, "Failed to allocate hello.\n");
+ mncc_sock_close(mncc);
+ return;
+ }
+
+ hello = (struct gsm_mncc_hello *) msgb_put(msg, sizeof(*hello));
+ hello->msg_type = MNCC_SOCKET_HELLO;
+ hello->version = MNCC_SOCK_VERSION;
+ hello->mncc_size = sizeof(struct gsm_mncc);
+ hello->data_frame_size = sizeof(struct gsm_data_frame);
+ hello->called_offset = offsetof(struct gsm_mncc, called);
+ hello->signal_offset = offsetof(struct gsm_mncc, signal);
+ hello->emergency_offset = offsetof(struct gsm_mncc, emergency);
+ hello->lchan_type_offset = offsetof(struct gsm_mncc, lchan_type);
+
+ msgb_enqueue(&mncc->net->upqueue, msg);
+ mncc->conn_bfd.when |= BSC_FD_WRITE;
+}
+
+/* accept a new connection */
+static int mncc_sock_accept(struct osmo_fd *bfd, unsigned int flags)
+{
+ struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+ struct osmo_fd *conn_bfd = &state->conn_bfd;
+ struct sockaddr_un un_addr;
+ socklen_t len;
+ int rc;
+
+ len = sizeof(un_addr);
+ rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+ if (rc < 0) {
+ LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n");
+ return -1;
+ }
+
+ if (conn_bfd->fd >= 0) {
+ LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have "
+ "another active connection ?!?\n");
+ /* We already have one MNCC app connected, this is all we support */
+ state->listen_bfd.when &= ~BSC_FD_READ;
+ close(rc);
+ return 0;
+ }
+
+ conn_bfd->fd = rc;
+ conn_bfd->when = BSC_FD_READ;
+ conn_bfd->cb = mncc_sock_cb;
+ conn_bfd->data = state;
+
+ if (osmo_fd_register(conn_bfd) != 0) {
+ LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n");
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+ return -1;
+ }
+
+ LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external "
+ "call control application\n");
+
+ queue_hello(state);
+ return 0;
+}
+
+
+int mncc_sock_init(struct gsm_network *net, const char *sock_path)
+{
+ struct mncc_sock_state *state;
+ struct osmo_fd *bfd;
+ int rc;
+
+ state = talloc_zero(tall_bsc_ctx, struct mncc_sock_state);
+ if (!state)
+ return -ENOMEM;
+
+ state->net = net;
+ state->conn_bfd.fd = -1;
+
+ bfd = &state->listen_bfd;
+
+ bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, sock_path,
+ OSMO_SOCK_F_BIND);
+ if (bfd->fd < 0) {
+ LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s: %s\n",
+ sock_path, strerror(errno));
+ talloc_free(state);
+ return -1;
+ }
+
+ bfd->when = BSC_FD_READ;
+ bfd->cb = mncc_sock_accept;
+ bfd->data = state;
+
+ rc = osmo_fd_register(bfd);
+ if (rc < 0) {
+ LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
+ close(bfd->fd);
+ talloc_free(state);
+ return rc;
+ }
+
+ net->mncc_state = state;
+
+ LOGP(DMNCC, LOGL_NOTICE, "MNCC socket at %s\n", sock_path);
+ return 0;
+}
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
new file mode 100644
index 000000000..2389980d3
--- /dev/null
+++ b/src/libmsc/osmo_msc.c
@@ -0,0 +1,177 @@
+/* main MSC management code... */
+
+/*
+ * (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/debug.h>
+#include <openbsc/transaction.h>
+#include <openbsc/db.h>
+
+#include <openbsc/gsm_04_11.h>
+
+static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+ int sapi = dlci & 0x7;
+
+ if (sapi == UM_SAPI_SMS)
+ gsm411_sapi_n_reject(conn);
+}
+
+static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+ gsm0408_clear_request(conn, cause);
+ return 1;
+}
+
+static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+ uint16_t chosen_channel)
+{
+ gsm0408_new_conn(conn);
+ gsm0408_dispatch(conn, msg);
+
+ /*
+ * If this is a silent call we want the channel to remain open as long as
+ * possible and this is why we accept this connection regardless of any
+ * pending transaction or ongoing operation.
+ */
+ if (conn->silent_call)
+ return BSC_API_CONN_POL_ACCEPT;
+ if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+ return BSC_API_CONN_POL_ACCEPT;
+ if (trans_has_conn(conn))
+ return BSC_API_CONN_POL_ACCEPT;
+
+ LOGP(DRR, LOGL_INFO, "MSC Complete L3: Rejecting connection.\n");
+ return BSC_API_CONN_POL_REJECT;
+}
+
+static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+ gsm0408_dispatch(conn, msg);
+}
+
+static void msc_assign_compl(struct gsm_subscriber_connection *conn,
+ uint8_t rr_cause, uint8_t chosen_channel,
+ uint8_t encr_alg_id, uint8_t speec)
+{
+ LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n");
+}
+
+static void msc_assign_fail(struct gsm_subscriber_connection *conn,
+ uint8_t cause, uint8_t *rr_cause)
+{
+ LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n");
+}
+
+static void msc_classmark_chg(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len)
+{
+ struct gsm_subscriber *subscr = conn->subscr;
+
+ if (subscr) {
+ subscr->equipment.classmark2_len = cm2_len;
+ memcpy(subscr->equipment.classmark2, cm2, cm2_len);
+ if (cm3) {
+ subscr->equipment.classmark3_len = cm3_len;
+ memcpy(subscr->equipment.classmark3, cm3, cm3_len);
+ }
+ db_sync_equipment(&subscr->equipment);
+ }
+}
+
+static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, uint8_t alg_id)
+{
+ gsm_cbfn *cb;
+
+ DEBUGP(DRR, "CIPHERING MODE COMPLETE\n");
+
+ /* Safety check */
+ if (!conn->sec_operation) {
+ DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n");
+ return;
+ }
+
+ /* FIXME: check for MI (if any) */
+
+ /* Call back whatever was in progress (if anything) ... */
+ cb = conn->sec_operation->cb;
+ if (cb) {
+ cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED,
+ NULL, conn, conn->sec_operation->cb_data);
+
+ }
+
+ /* Complete the operation */
+ release_security_operation(conn);
+}
+
+
+
+static struct bsc_api msc_handler = {
+ .sapi_n_reject = msc_sapi_n_reject,
+ .compl_l3 = msc_compl_l3,
+ .dtap = msc_dtap,
+ .clear_request = msc_clear_request,
+ .assign_compl = msc_assign_compl,
+ .assign_fail = msc_assign_fail,
+ .classmark_chg = msc_classmark_chg,
+ .cipher_mode_compl = msc_ciph_m_compl,
+};
+
+struct bsc_api *msc_bsc_api() {
+ return &msc_handler;
+}
+
+/* lchan release handling */
+void msc_release_connection(struct gsm_subscriber_connection *conn)
+{
+ /* skip when we are in release, e.g. due an error */
+ if (conn->in_release)
+ return;
+
+ /* skip releasing of silent calls as they have no transaction */
+ if (conn->silent_call)
+ return;
+
+ /* check if there is a pending operation */
+ if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+ return;
+
+ if (trans_has_conn(conn))
+ return;
+
+ /* no more connections, asking to release the channel */
+
+ /*
+ * We had stopped the LU expire timer T3212. Now we are about
+ * to send the MS back to the idle state and this should lead
+ * to restarting the timer. Set the new expiration time.
+ */
+ if (conn->expire_timer_stopped)
+ subscr_update_expire_lu(conn->subscr, conn->bts);
+
+ conn->in_release = 1;
+ gsm0808_clear(conn);
+ msc_subscr_con_free(conn);
+}
diff --git a/src/libmsc/rrlp.c b/src/libmsc/rrlp.c
new file mode 100644
index 000000000..e695daac7
--- /dev/null
+++ b/src/libmsc/rrlp.c
@@ -0,0 +1,104 @@
+/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+/* RRLP msPositionReq, nsBased,
+ * Accuracy=60, Method=gps, ResponseTime=2, oneSet */
+static const uint8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 };
+
+/* RRLP msPositionReq, msBasedPref,
+ Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const uint8_t ms_pref_pos_req[] = { 0x40, 0x02, 0x79, 0x50 };
+
+/* RRLP msPositionReq, msAssistedPref,
+ Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const uint8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 };
+
+static int send_rrlp_req(struct gsm_subscriber_connection *conn)
+{
+ struct gsm_network *net = conn->network;
+ const uint8_t *req;
+
+ switch (net->rrlp.mode) {
+ case RRLP_MODE_MS_BASED:
+ req = ms_based_pos_req;
+ break;
+ case RRLP_MODE_MS_PREF:
+ req = ms_pref_pos_req;
+ break;
+ case RRLP_MODE_ASS_PREF:
+ req = ass_pref_pos_req;
+ break;
+ case RRLP_MODE_NONE:
+ default:
+ return 0;
+ }
+
+ return gsm48_send_rr_app_info(conn, 0x00,
+ sizeof(ms_based_pos_req), req);
+}
+
+static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_subscriber *subscr;
+ struct gsm_subscriber_connection *conn;
+
+ switch (signal) {
+ case S_SUBSCR_ATTACHED:
+ /* A subscriber has attached. */
+ subscr = signal_data;
+ conn = connection_for_subscr(subscr);
+ if (!conn)
+ break;
+ send_rrlp_req(conn);
+ break;
+ }
+ return 0;
+}
+
+static int paging_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct paging_signal_data *psig_data = signal_data;
+
+ switch (signal) {
+ case S_PAGING_SUCCEEDED:
+ /* A subscriber has attached. */
+ send_rrlp_req(psig_data->conn);
+ break;
+ case S_PAGING_EXPIRED:
+ break;
+ }
+ return 0;
+}
+
+void on_dso_load_rrlp(void)
+{
+ osmo_signal_register_handler(SS_SUBSCR, subscr_sig_cb, NULL);
+ osmo_signal_register_handler(SS_PAGING, paging_sig_cb, NULL);
+}
diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c
new file mode 100644
index 000000000..590d01bbf
--- /dev/null
+++ b/src/libmsc/silent_call.c
@@ -0,0 +1,152 @@
+/* GSM silent call feature */
+
+/*
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/osmo_msc.h>
+
+/* paging of the requested subscriber has completed */
+static int paging_cb_silent(unsigned int hooknum, unsigned int event,
+ struct msgb *msg, void *_conn, void *_data)
+{
+ struct gsm_subscriber_connection *conn = _conn;
+ struct scall_signal_data sigdata;
+ int rc = 0;
+
+ if (hooknum != GSM_HOOK_RR_PAGING)
+ return -EINVAL;
+
+ DEBUGP(DLSMS, "paging_cb_silent: ");
+
+ sigdata.conn = conn;
+ sigdata.data = _data;
+
+ switch (event) {
+ case GSM_PAGING_SUCCEEDED:
+ DEBUGPC(DLSMS, "success, using Timeslot %u on ARFCN %u\n",
+ conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
+ conn->silent_call = 1;
+ /* increment lchan reference count */
+ osmo_signal_dispatch(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
+ break;
+ case GSM_PAGING_EXPIRED:
+ case GSM_PAGING_BUSY:
+ case GSM_PAGING_OOM:
+ DEBUGP(DLSMS, "expired\n");
+ osmo_signal_dispatch(SS_SCALL, S_SCALL_EXPIRED, &sigdata);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+#if 0
+/* receive a layer 3 message from a silent call */
+int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ /* FIXME: do something like sending it through a UDP port */
+ LOGP(DLSMS, LOGL_NOTICE, "Discarding L3 message from a silent call.\n");
+ return 0;
+}
+#endif
+
+struct msg_match {
+ uint8_t pdisc;
+ uint8_t msg_type;
+};
+
+/* list of messages that are handled inside OpenBSC, even in a silent call */
+static const struct msg_match silent_call_accept[] = {
+ { GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST },
+ { GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ },
+};
+
+#if 0
+/* decide if we need to reroute a message as part of a silent call */
+int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t msg_type = gsm48_hdr_msg_type(gh);
+ int i;
+
+ /* if we're not part of a silent call, never reroute */
+ if (!conn->silent_call)
+ return 0;
+
+ /* check if we are a special message that is handled in openbsc */
+ for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) {
+ if (silent_call_accept[i].pdisc == pdisc &&
+ silent_call_accept[i].msg_type == msg_type)
+ return 0;
+ }
+
+ /* otherwise, reroute */
+ LOGP(DLSMS, LOGL_INFO, "Rerouting L3 message from a silent call.\n");
+ return 1;
+}
+#endif
+
+
+/* initiate a silent call with a given subscriber */
+int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type)
+{
+ struct subscr_request *req;
+
+ req = subscr_request_channel(subscr, type, paging_cb_silent, data);
+ return req != NULL;
+}
+
+/* end a silent call with a given subscriber */
+int gsm_silent_call_stop(struct gsm_subscriber *subscr)
+{
+ struct gsm_subscriber_connection *conn;
+
+ conn = connection_for_subscr(subscr);
+ if (!conn)
+ return -EINVAL;
+
+ /* did we actually establish a silent call for this guy? */
+ if (!conn->silent_call)
+ return -EINVAL;
+
+ DEBUGPC(DLSMS, "Stopping silent call using Timeslot %u on ARFCN %u\n",
+ conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
+
+ conn->silent_call = 0;
+ msc_release_connection(conn);
+
+ return 0;
+}
diff --git a/src/libmsc/smpp_openbsc.c b/src/libmsc/smpp_openbsc.c
new file mode 100644
index 000000000..f94968a3f
--- /dev/null
+++ b/src/libmsc/smpp_openbsc.c
@@ -0,0 +1,758 @@
+/* OpenBSC SMPP 3.4 interface, SMSC-side implementation */
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+#include <osmocom/gsm/protocol/smpp34_osmocom.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/transaction.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+#include "smpp_smsc.h"
+
+/*! \brief find gsm_subscriber for a given SMPP NPI/TON/Address */
+static struct gsm_subscriber *subscr_by_dst(struct gsm_network *net,
+ uint8_t npi, uint8_t ton, const char *addr)
+{
+ struct gsm_subscriber *subscr = NULL;
+
+ switch (npi) {
+ case NPI_Land_Mobile_E212:
+ subscr = subscr_get_by_imsi(net->subscr_group, addr);
+ break;
+ case NPI_ISDN_E163_E164:
+ case NPI_Private:
+ subscr = subscr_get_by_extension(net->subscr_group, addr);
+ break;
+ default:
+ LOGP(DSMPP, LOGL_NOTICE, "Unsupported NPI: %u\n", npi);
+ break;
+ }
+
+ /* tag the context in case we know it */
+ log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+ return subscr;
+}
+
+/*! \brief find a TLV with given tag in list of libsmpp34 TLVs */
+static struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag)
+{
+ struct tlv_t *t;
+
+ for (t = head; t != NULL; t = t->next) {
+ if (t->tag == tag)
+ return t;
+ }
+ return NULL;
+}
+
+/*! \brief convert from submit_sm_t to gsm_sms */
+static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net,
+ const struct submit_sm_t *submit)
+{
+ struct gsm_subscriber *dest;
+ struct gsm_sms *sms;
+ struct tlv_t *t;
+ const uint8_t *sms_msg;
+ unsigned int sms_msg_len;
+ int mode;
+
+ dest = subscr_by_dst(net, submit->dest_addr_npi,
+ submit->dest_addr_ton,
+ (const char *)submit->destination_addr);
+ if (!dest) {
+ LOGP(DLSMS, LOGL_NOTICE, "SMPP SUBMIT-SM for unknown subscriber: "
+ "%s (NPI=%u)\n", submit->destination_addr,
+ submit->dest_addr_npi);
+ return ESME_RINVDSTADR;
+ }
+
+ t = find_tlv(submit->tlv, TLVID_message_payload);
+ if (t) {
+ if (submit->sm_length) {
+ /* ERROR: we cannot have both! */
+ LOGP(DLSMS, LOGL_ERROR, "SMPP Cannot have payload in "
+ "TLV _and_ in the header\n");
+ subscr_put(dest);
+ return ESME_ROPTPARNOTALLWD;
+ }
+ sms_msg = t->value.octet;
+ sms_msg_len = t->length;
+ } else if (submit->sm_length > 0 && submit->sm_length < 255) {
+ sms_msg = submit->short_message;
+ sms_msg_len = submit->sm_length;
+ } else {
+ LOGP(DLSMS, LOGL_ERROR,
+ "SMPP neither message payload nor valid sm_length.\n");
+ subscr_put(dest);
+ return ESME_RINVPARLEN;
+ }
+
+ sms = sms_alloc();
+ sms->source = SMS_SOURCE_SMPP;
+ sms->smpp.sequence_nr = submit->sequence_number;
+
+ /* fill in the destination address */
+ sms->receiver = dest;
+ sms->dst.ton = submit->dest_addr_ton;
+ sms->dst.npi = submit->dest_addr_npi;
+ osmo_strlcpy(sms->dst.addr, dest->extension, sizeof(sms->dst.addr));
+
+ /* fill in the source address */
+ sms->src.ton = submit->source_addr_ton;
+ sms->src.npi = submit->source_addr_npi;
+ osmo_strlcpy(sms->src.addr, (char *)submit->source_addr,
+ sizeof(sms->src.addr));
+
+ if (submit->esm_class & 0x40)
+ sms->ud_hdr_ind = 1;
+
+ if (submit->esm_class & 0x80) {
+ sms->reply_path_req = 1;
+#warning Implement reply path
+ }
+
+ if (submit->data_coding == 0x00 || /* SMSC default */
+ submit->data_coding == 0x01) { /* GSM default alphabet */
+ sms->data_coding_scheme = GSM338_DCS_1111_7BIT;
+ mode = MODE_7BIT;
+ } else if ((submit->data_coding & 0xFC) == 0xF0) { /* 03.38 DCS default */
+ /* pass DCS 1:1 through from SMPP to GSM */
+ sms->data_coding_scheme = submit->data_coding;
+ mode = MODE_7BIT;
+ } else if (submit->data_coding == 0x02 ||
+ submit->data_coding == 0x04) {
+ /* 8-bit binary */
+ sms->data_coding_scheme = GSM338_DCS_1111_8BIT_DATA;
+ mode = MODE_8BIT;
+ } else if ((submit->data_coding & 0xFC) == 0xF4) { /* 03.38 DCS 8bit */
+ /* pass DCS 1:1 through from SMPP to GSM */
+ sms->data_coding_scheme = submit->data_coding;
+ mode = MODE_8BIT;
+ } else if (submit->data_coding == 0x08) {
+ /* UCS-2 */
+ sms->data_coding_scheme = (2 << 2);
+ mode = MODE_8BIT;
+ } else {
+ sms_free(sms);
+ LOGP(DLSMS, LOGL_ERROR, "SMPP Unknown Data Coding 0x%02x\n",
+ submit->data_coding);
+ return ESME_RUNKNOWNERR;
+ }
+
+ if (mode == MODE_7BIT) {
+ uint8_t ud_len = 0, padbits = 0;
+ sms->data_coding_scheme = GSM338_DCS_1111_7BIT;
+ if (sms->ud_hdr_ind) {
+ ud_len = *sms_msg + 1;
+ printf("copying %u bytes user data...\n", ud_len);
+ memcpy(sms->user_data, sms_msg,
+ OSMO_MIN(ud_len, sizeof(sms->user_data)));
+ sms_msg += ud_len;
+ sms_msg_len -= ud_len;
+ padbits = 7 - (ud_len % 7);
+ }
+ gsm_septets2octets(sms->user_data+ud_len, sms_msg,
+ sms_msg_len, padbits);
+ sms->user_data_len = (ud_len*8 + padbits)/7 + sms_msg_len;/* SEPTETS */
+ /* FIXME: sms->text */
+ } else {
+ memcpy(sms->user_data, sms_msg, sms_msg_len);
+ sms->user_data_len = sms_msg_len;
+ }
+
+ *psms = sms;
+ return ESME_ROK;
+}
+
+/*! \brief handle incoming libsmpp34 ssubmit_sm_t from remote ESME */
+int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
+ struct submit_sm_resp_t *submit_r)
+{
+ struct gsm_sms *sms;
+ struct gsm_network *net = esme->smsc->priv;
+ struct sms_signal_data sig;
+ int rc = -1;
+
+ rc = submit_to_sms(&sms, net, submit);
+ if (rc != ESME_ROK) {
+ submit_r->command_status = rc;
+ return 0;
+ }
+ smpp_esme_get(esme);
+ sms->smpp.esme = esme;
+ sms->protocol_id = submit->protocol_id;
+
+ switch (submit->esm_class & 3) {
+ case 0: /* default */
+ case 1: /* datagram */
+ case 3: /* store-and-forward */
+ rc = db_sms_store(sms);
+ sms_free(sms);
+ sms = NULL;
+ if (rc < 0) {
+ LOGP(DLSMS, LOGL_ERROR, "SMPP SUBMIT-SM: Unable to "
+ "store SMS in database\n");
+ submit_r->command_status = ESME_RSYSERR;
+ return 0;
+ }
+ strcpy((char *)submit_r->message_id, "msg_id_not_implemented");
+ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Stored in DB\n");
+
+ memset(&sig, 0, sizeof(sig));
+ osmo_signal_dispatch(SS_SMS, S_SMS_SUBMITTED, &sig);
+ rc = 0;
+ break;
+ case 2: /* forward (i.e. transaction) mode */
+ LOGP(DLSMS, LOGL_DEBUG, "SMPP SUBMIT-SM: Forwarding in "
+ "real time (Transaction/Forward mode)\n");
+ sms->smpp.transaction_mode = 1;
+ gsm411_send_sms_subscr(sms->receiver, sms);
+ rc = 1; /* don't send any response yet */
+ break;
+ }
+ return rc;
+}
+
+static void alert_all_esme(struct smsc *smsc, struct gsm_subscriber *subscr,
+ uint8_t smpp_avail_status)
+{
+ struct osmo_esme *esme;
+
+ llist_for_each_entry(esme, &smsc->esme_list, list) {
+ /* we currently send an alert notification to each ESME that is
+ * connected, and do not require a (non-existant) delivery
+ * pending flag to be set before, FIXME: make this VTY
+ * configurable */
+ if (esme->acl && esme->acl->deliver_src_imsi) {
+ smpp_tx_alert(esme, TON_Subscriber_Number,
+ NPI_Land_Mobile_E212,
+ subscr->imsi, smpp_avail_status);
+ } else {
+ smpp_tx_alert(esme, TON_Network_Specific,
+ NPI_ISDN_E163_E164,
+ subscr->extension, smpp_avail_status);
+ }
+ }
+}
+
+
+/*! \brief signal handler for status of attempted SMS deliveries */
+static int smpp_sms_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct sms_signal_data *sig_sms = signal_data;
+ struct gsm_sms *sms = sig_sms->sms;
+ struct smsc *smsc = handler_data;
+ int rc = 0;
+
+ if (!sms)
+ return 0;
+
+ if (sms->source != SMS_SOURCE_SMPP)
+ return 0;
+
+ switch (signal) {
+ case S_SMS_MEM_EXCEEDED:
+ /* fall-through: There is no ESME_Rxxx result code to
+ * indicate a MEMORY EXCEEDED in transaction mode back
+ * to the ESME */
+ case S_SMS_UNKNOWN_ERROR:
+ if (sms->smpp.transaction_mode) {
+ /* Send back the SUBMIT-SM response with apropriate error */
+ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Error\n");
+ rc = smpp_tx_submit_r(sms->smpp.esme,
+ sms->smpp.sequence_nr,
+ ESME_RDELIVERYFAILURE,
+ sms->smpp.msg_id);
+ }
+ break;
+ case S_SMS_DELIVERED:
+ /* SMS layer tells us the delivery has been completed */
+ if (sms->smpp.transaction_mode) {
+ /* Send back the SUBMIT-SM response */
+ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Success\n");
+ rc = smpp_tx_submit_r(sms->smpp.esme,
+ sms->smpp.sequence_nr,
+ ESME_ROK, sms->smpp.msg_id);
+ }
+ break;
+ case S_SMS_SMMA:
+ if (!sig_sms->trans || !sig_sms->trans->subscr) {
+ /* SMMA without a subscriber? strange... */
+ LOGP(DLSMS, LOGL_NOTICE, "SMMA without subscriber?\n");
+ break;
+ }
+
+ /* There's no real 1:1 match for SMMA in SMPP. However,
+ * an ALERT NOTIFICATION seems to be the most logical
+ * choice */
+ alert_all_esme(smsc, sig_sms->trans->subscr, 0);
+ break;
+ }
+
+ return rc;
+}
+
+/*! \brief signal handler for subscriber related signals */
+static int smpp_subscr_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_subscriber *subscr = signal_data;
+ struct smsc *smsc = handler_data;
+ uint8_t smpp_avail_status;
+
+ /* determine the smpp_avail_status depending on attach/detach */
+ switch (signal) {
+ case S_SUBSCR_ATTACHED:
+ smpp_avail_status = 0;
+ break;
+ case S_SUBSCR_DETACHED:
+ smpp_avail_status = 2;
+ break;
+ default:
+ return 0;
+ }
+
+ alert_all_esme(smsc, subscr, smpp_avail_status);
+
+ return 0;
+}
+
+/* GSM 03.38 6.2.1 Character expanding (no decode!) */
+static int gsm_7bit_expand(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind)
+{
+ int i = 0;
+ int shift = 0;
+ uint8_t c;
+
+ /* skip the user data header */
+ if (ud_hdr_ind) {
+ /* get user data header length + 1 (for the 'user data header length'-field) */
+ shift = ((user_data[0] + 1) * 8) / 7;
+ if ((((user_data[0] + 1) * 8) % 7) != 0)
+ shift++;
+ septet_l = septet_l - shift;
+ }
+
+ for (i = 0; i < septet_l; i++) {
+ c =
+ ((user_data[((i + shift) * 7 + 7) >> 3] <<
+ (7 - (((i + shift) * 7 + 7) & 7))) |
+ (user_data[((i + shift) * 7) >> 3] >>
+ (((i + shift) * 7) & 7))) & 0x7f;
+
+ *(text++) = c;
+ }
+
+ *text = '\0';
+
+ return i;
+}
+
+
+/* FIXME: libsmpp34 helpers, they should be part of libsmpp34! */
+void append_tlv(tlv_t **req_tlv, uint16_t tag,
+ const uint8_t *data, uint16_t len)
+{
+ tlv_t tlv;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.tag = tag;
+ tlv.length = len;
+ memcpy(tlv.value.octet, data, tlv.length);
+ build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u8(tlv_t **req_tlv, uint16_t tag, uint8_t val)
+{
+ tlv_t tlv;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.tag = tag;
+ tlv.length = 1;
+ tlv.value.val08 = val;
+ build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u16(tlv_t **req_tlv, uint16_t tag, uint16_t val)
+{
+ tlv_t tlv;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.tag = tag;
+ tlv.length = 2;
+ tlv.value.val16 = htons(val);
+ build_tlv(req_tlv, &tlv);
+}
+
+/* Append the Osmocom vendor-specific additional TLVs to a SMPP msg */
+static void append_osmo_tlvs(tlv_t **req_tlv, const struct gsm_lchan *lchan)
+{
+ int idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, 1);
+ const struct gsm_meas_rep *mr = &lchan->meas_rep[idx];
+ const struct gsm_meas_rep_unidir *ul_meas = &mr->ul;
+ const struct gsm_meas_rep_unidir *dl_meas = &mr->dl;
+
+ /* Osmocom vendor-specific SMPP34 extensions */
+ append_tlv_u16(req_tlv, TLVID_osmo_arfcn, lchan->ts->trx->arfcn);
+ if (mr->flags & MEAS_REP_F_MS_L1) {
+ uint8_t ms_dbm;
+ append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_l1.ta);
+ ms_dbm = ms_pwr_dbm(lchan->ts->trx->bts->band, mr->ms_l1.pwr);
+ append_tlv_u8(req_tlv, TLVID_osmo_ms_l1_txpwr, ms_dbm);
+ } else if (mr->flags & MEAS_REP_F_MS_TO) /* Save Timing Offset field = MS Timing Offset + 63 */
+ append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_timing_offset + 63);
+
+ append_tlv_u16(req_tlv, TLVID_osmo_rxlev_ul,
+ rxlev2dbm(ul_meas->full.rx_lev));
+ append_tlv_u8(req_tlv, TLVID_osmo_rxqual_ul, ul_meas->full.rx_qual);
+
+ if (mr->flags & MEAS_REP_F_DL_VALID) {
+ append_tlv_u16(req_tlv, TLVID_osmo_rxlev_dl,
+ rxlev2dbm(dl_meas->full.rx_lev));
+ append_tlv_u8(req_tlv, TLVID_osmo_rxqual_dl,
+ dl_meas->full.rx_qual);
+ }
+
+ if (lchan->conn && lchan->conn->subscr) {
+ struct gsm_subscriber *subscr = lchan->conn->subscr;
+ size_t imei_len = strlen(subscr->equipment.imei);
+ if (imei_len)
+ append_tlv(req_tlv, TLVID_osmo_imei,
+ (uint8_t *)subscr->equipment.imei, imei_len+1);
+ }
+}
+
+struct {
+ uint32_t smpp_status_code;
+ uint8_t gsm411_cause;
+} smpp_to_gsm411_err_array[] = {
+
+ /* Seems like most phones don't care about the failure cause,
+ * although some will display a different notification for
+ * GSM411_RP_CAUSE_MO_NUM_UNASSIGNED
+ * Some provoke a display of "Try again later"
+ * while others a more definitive "Message sending failed"
+ */
+
+ { ESME_RSYSERR, GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER },
+ { ESME_RINVDSTADR, GSM411_RP_CAUSE_MO_NUM_UNASSIGNED },
+ { ESME_RMSGQFUL, GSM411_RP_CAUSE_MO_CONGESTION },
+ { ESME_RINVSRCADR, GSM411_RP_CAUSE_MO_SMS_REJECTED },
+ { ESME_RINVMSGID, GSM411_RP_CAUSE_INV_TRANS_REF }
+};
+
+static int smpp_to_gsm411_err(uint32_t smpp_status_code, int *gsm411_cause)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(smpp_to_gsm411_err_array); i++) {
+ if (smpp_to_gsm411_err_array[i].smpp_status_code != smpp_status_code)
+ continue;
+ *gsm411_cause = smpp_to_gsm411_err_array[i].gsm411_cause;
+ return 0;
+ }
+ return -1;
+}
+
+static void smpp_cmd_free(struct osmo_smpp_cmd *cmd)
+{
+ osmo_timer_del(&cmd->response_timer);
+ llist_del(&cmd->list);
+ subscr_put(cmd->subscr);
+ sms_free(cmd->sms);
+ talloc_free(cmd);
+}
+
+void smpp_cmd_flush_pending(struct osmo_esme *esme)
+{
+ struct osmo_smpp_cmd *cmd, *next;
+
+ llist_for_each_entry_safe(cmd, next, &esme->smpp_cmd_list, list)
+ smpp_cmd_free(cmd);
+}
+
+void smpp_cmd_ack(struct osmo_smpp_cmd *cmd)
+{
+ struct gsm_subscriber_connection *conn;
+ struct gsm_trans *trans;
+
+ conn = connection_for_subscr(cmd->subscr);
+ if (!conn) {
+ LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
+ return;
+ }
+
+ trans = trans_find_by_id(conn, GSM48_PDISC_SMS,
+ cmd->sms->gsm411.transaction_id);
+ if (!trans) {
+ LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
+ cmd->sms->gsm411.transaction_id);
+ return;
+ }
+
+ gsm411_send_rp_ack(trans, cmd->sms->gsm411.msg_ref);
+ smpp_cmd_free(cmd);
+}
+
+void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status)
+{
+ struct gsm_subscriber_connection *conn;
+ struct gsm_trans *trans;
+ int gsm411_cause;
+
+ conn = connection_for_subscr(cmd->subscr);
+ if (!conn) {
+ LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
+ return;
+ }
+
+ trans = trans_find_by_id(conn, GSM48_PDISC_SMS,
+ cmd->sms->gsm411.transaction_id);
+ if (!trans) {
+ LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
+ cmd->sms->gsm411.transaction_id);
+ return;
+ }
+
+ if (smpp_to_gsm411_err(status, &gsm411_cause) < 0)
+ gsm411_cause = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+ gsm411_send_rp_error(trans, cmd->sms->gsm411.msg_ref, gsm411_cause);
+
+ smpp_cmd_free(cmd);
+}
+
+static void smpp_deliver_sm_cb(void *data)
+{
+ smpp_cmd_err(data, ESME_RSYSERR);
+}
+
+static int smpp_cmd_enqueue(struct osmo_esme *esme,
+ struct gsm_subscriber *subscr, struct gsm_sms *sms,
+ uint32_t sequence_number, bool *deferred)
+{
+ struct osmo_smpp_cmd *cmd;
+
+ cmd = talloc_zero(esme, struct osmo_smpp_cmd);
+ if (!cmd)
+ return -1;
+
+ cmd->sequence_nr = sequence_number;
+ cmd->sms = sms;
+ cmd->subscr = subscr_get(subscr);
+
+ /* FIXME: No predefined value for this response_timer as specified by
+ * SMPP 3.4 specs, section 7.2. Make this configurable? Don't forget
+ * lchan keeps busy until we get a reply to this SMPP command. Too high
+ * value may exhaust resources.
+ */
+ osmo_timer_setup(&cmd->response_timer, smpp_deliver_sm_cb, cmd);
+ osmo_timer_schedule(&cmd->response_timer, 5, 0);
+ llist_add_tail(&cmd->list, &esme->smpp_cmd_list);
+ *deferred = true;
+
+ return 0;
+}
+
+struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme,
+ uint32_t sequence_nr)
+{
+ struct osmo_smpp_cmd *cmd;
+
+ llist_for_each_entry(cmd, &esme->smpp_cmd_list, list) {
+ if (cmd->sequence_nr == sequence_nr)
+ return cmd;
+ }
+ return NULL;
+}
+
+static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
+ struct gsm_subscriber_connection *conn,
+ bool *deferred)
+{
+ struct deliver_sm_t deliver;
+ int mode, ret;
+ uint8_t dcs;
+
+ memset(&deliver, 0, sizeof(deliver));
+ deliver.command_length = 0;
+ deliver.command_id = DELIVER_SM;
+ deliver.command_status = ESME_ROK;
+
+ strcpy((char *)deliver.service_type, "CMT");
+ if (esme->acl && esme->acl->deliver_src_imsi) {
+ deliver.source_addr_ton = TON_Subscriber_Number;
+ deliver.source_addr_npi = NPI_Land_Mobile_E212;
+ snprintf((char *)deliver.source_addr,
+ sizeof(deliver.source_addr), "%s",
+ conn->subscr->imsi);
+ } else {
+ deliver.source_addr_ton = TON_Network_Specific;
+ deliver.source_addr_npi = NPI_ISDN_E163_E164;
+ snprintf((char *)deliver.source_addr,
+ sizeof(deliver.source_addr), "%s",
+ conn->subscr->extension);
+ }
+
+ deliver.dest_addr_ton = sms->dst.ton;
+ deliver.dest_addr_npi = sms->dst.npi;
+ memcpy(deliver.destination_addr, sms->dst.addr,
+ sizeof(deliver.destination_addr));
+
+ deliver.esm_class = 1; /* datagram mode */
+ if (sms->ud_hdr_ind)
+ deliver.esm_class |= 0x40;
+ if (sms->reply_path_req)
+ deliver.esm_class |= 0x80;
+
+ deliver.protocol_id = sms->protocol_id;
+ deliver.priority_flag = 0;
+ deliver.registered_delivery = 0;
+
+ /* Figure out SMPP DCS from TP-DCS */
+ dcs = sms->data_coding_scheme;
+ if (smpp_determine_scheme(dcs, &deliver.data_coding, &mode) == -1)
+ return -1;
+
+ /* Transparently pass on DCS via SMPP if requested */
+ if (esme->acl && esme->acl->dcs_transparent)
+ deliver.data_coding = dcs;
+
+ if (mode == MODE_7BIT) {
+ uint8_t *dst = deliver.short_message;
+
+ /* SMPP has this strange notion of putting 7bit SMS in
+ * an octet-aligned mode */
+ if (sms->ud_hdr_ind) {
+ /* length (bytes) of UDH inside UD */
+ uint8_t udh_len = sms->user_data[0] + 1;
+
+ /* copy over the UDH */
+ memcpy(dst, sms->user_data, udh_len);
+ dst += udh_len;
+ deliver.sm_length = udh_len;
+ }
+ /* add decoded text */
+ deliver.sm_length += gsm_7bit_expand((char *)dst, sms->user_data, sms->user_data_len, sms->ud_hdr_ind);
+ } else {
+ deliver.sm_length = sms->user_data_len;
+ memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
+ deliver.sm_length = sms->user_data_len;
+ memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
+ }
+
+ if (esme->acl && esme->acl->osmocom_ext && conn->lchan)
+ append_osmo_tlvs(&deliver.tlv, conn->lchan);
+
+ ret = smpp_tx_deliver(esme, &deliver);
+ if (ret < 0)
+ return ret;
+
+ return smpp_cmd_enqueue(esme, conn->subscr, sms,
+ deliver.sequence_number, deferred);
+}
+
+static struct smsc *g_smsc;
+
+int smpp_route_smpp_first(struct gsm_sms *sms, struct gsm_subscriber_connection *conn)
+{
+ return g_smsc->smpp_first;
+}
+
+int smpp_try_deliver(struct gsm_sms *sms,
+ struct gsm_subscriber_connection *conn, bool *deferred)
+{
+ struct osmo_esme *esme;
+ struct osmo_smpp_addr dst;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.ton = sms->dst.ton;
+ dst.npi = sms->dst.npi;
+ memcpy(dst.addr, sms->dst.addr, sizeof(dst.addr));
+
+ esme = smpp_route(g_smsc, &dst);
+ if (!esme)
+ return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+
+ return deliver_to_esme(esme, sms, conn, deferred);
+}
+
+struct smsc *smsc_from_vty(struct vty *v)
+{
+ /* FIXME: this is ugly */
+ return g_smsc;
+}
+
+/*! \brief Allocate the OpenBSC SMPP interface struct and init VTY. */
+int smpp_openbsc_alloc_init(void *ctx)
+{
+ g_smsc = smpp_smsc_alloc_init(ctx);
+ if (!g_smsc) {
+ LOGP(DSMPP, LOGL_FATAL, "Cannot allocate smsc struct\n");
+ return -1;
+ }
+ return smpp_vty_init();
+}
+
+/*! \brief Launch the OpenBSC SMPP interface with the parameters set from VTY.
+ */
+int smpp_openbsc_start(struct gsm_network *net)
+{
+ int rc;
+ g_smsc->priv = net;
+
+ /* If a VTY configuration has taken place, the values have been stored
+ * in the smsc struct. Otherwise, use the defaults (NULL -> any, 0 ->
+ * default SMPP port, see smpp_smsc_bind()). */
+ rc = smpp_smsc_start(g_smsc, g_smsc->bind_addr, g_smsc->listen_port);
+ if (rc < 0)
+ return rc;
+
+ rc = osmo_signal_register_handler(SS_SMS, smpp_sms_cb, g_smsc);
+ if (rc < 0)
+ return rc;
+ rc = osmo_signal_register_handler(SS_SUBSCR, smpp_subscr_cb, g_smsc);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
diff --git a/src/libmsc/smpp_smsc.c b/src/libmsc/smpp_smsc.c
new file mode 100644
index 000000000..48a119261
--- /dev/null
+++ b/src/libmsc/smpp_smsc.c
@@ -0,0 +1,1030 @@
+/* SMPP 3.4 interface, SMSC-side implementation */
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+
+#include "smpp_smsc.h"
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+
+/*! \brief Ugly wrapper. libsmpp34 should do this itself! */
+#define SMPP34_UNPACK(rc, type, str, data, len) \
+ memset(str, 0, sizeof(*str)); \
+ rc = smpp34_unpack(type, str, data, len)
+
+enum emse_bind {
+ ESME_BIND_RX = 0x01,
+ ESME_BIND_TX = 0x02,
+};
+
+const struct value_string smpp_status_strs[] = {
+ { ESME_ROK, "No Error" },
+ { ESME_RINVMSGLEN, "Message Length is invalid" },
+ { ESME_RINVCMDLEN, "Command Length is invalid" },
+ { ESME_RINVCMDID, "Invalid Command ID" },
+ { ESME_RINVBNDSTS, "Incorrect BIND Status for given command" },
+ { ESME_RALYBND, "ESME Already in Bound State" },
+ { ESME_RINVPRTFLG, "Invalid Priority Flag" },
+ { ESME_RINVREGDLVFLG, "Invalid Registered Delivery Flag" },
+ { ESME_RSYSERR, "System Error" },
+ { ESME_RINVSRCADR, "Invalid Source Address" },
+ { ESME_RINVDSTADR, "Invalid Destination Address" },
+ { ESME_RINVMSGID, "Message ID is invalid" },
+ { ESME_RBINDFAIL, "Bind failed" },
+ { ESME_RINVPASWD, "Invalid Password" },
+ { ESME_RINVSYSID, "Invalid System ID" },
+ { ESME_RCANCELFAIL, "Cancel SM Failed" },
+ { ESME_RREPLACEFAIL, "Replace SM Failed" },
+ { ESME_RMSGQFUL, "Message Queue Full" },
+ { ESME_RINVSERTYP, "Invalid Service Type" },
+ { ESME_RINVNUMDESTS, "Invalid number of destinations" },
+ { ESME_RINVDLNAME, "Invalid Distribution List name" },
+ { ESME_RINVDESTFLAG, "Destination flag is invalid" },
+ { ESME_RINVSUBREP, "Invalid submit with replace request" },
+ { ESME_RINVESMCLASS, "Invalid esm_class field data" },
+ { ESME_RCNTSUBDL, "Cannot Submit to Distribution List" },
+ { ESME_RSUBMITFAIL, "submit_sm or submit_multi failed" },
+ { ESME_RINVSRCTON, "Invalid Source address TON" },
+ { ESME_RINVSRCNPI, "Invalid Sourec address NPI" },
+ { ESME_RINVDSTTON, "Invalid Destination address TON" },
+ { ESME_RINVDSTNPI, "Invalid Desetination address NPI" },
+ { ESME_RINVSYSTYP, "Invalid system_type field" },
+ { ESME_RINVREPFLAG, "Invalid replace_if_present field" },
+ { ESME_RINVNUMMSGS, "Invalid number of messages" },
+ { ESME_RTHROTTLED, "Throttling error (ESME has exceeded message limits)" },
+ { ESME_RINVSCHED, "Invalid Scheduled Delivery Time" },
+ { ESME_RINVEXPIRY, "Invalid message validity period (Expiry time)" },
+ { ESME_RINVDFTMSGID, "Predefined Message Invalid or Not Found" },
+ { ESME_RX_T_APPN, "ESME Receiver Temporary App Error Code" },
+ { ESME_RX_P_APPN, "ESME Receiver Permanent App Error Code" },
+ { ESME_RX_R_APPN, "ESME Receiver Reject Message Error Code" },
+ { ESME_RQUERYFAIL, "query_sm request failed" },
+ { ESME_RINVOPTPARSTREAM,"Error in the optional part of the PDU Body" },
+ { ESME_ROPTPARNOTALLWD, "Optional Parameter not allowed" },
+ { ESME_RINVPARLEN, "Invalid Parameter Length" },
+ { ESME_RMISSINGOPTPARAM,"Expected Optional Parameter missing" },
+ { ESME_RINVOPTPARAMVAL, "Invalid Optional Parameter Value" },
+ { ESME_RDELIVERYFAILURE,"Delivery Failure (used for data_sm_resp)" },
+ { ESME_RUNKNOWNERR, "Unknown Error" },
+ { 0, NULL }
+};
+
+/*! \brief compare if two SMPP addresses are equal */
+int smpp_addr_eq(const struct osmo_smpp_addr *a,
+ const struct osmo_smpp_addr *b)
+{
+ if (a->ton == b->ton &&
+ a->npi == b->npi &&
+ !strcmp(a->addr, b->addr))
+ return 1;
+
+ return 0;
+}
+
+
+struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
+ const char *sys_id)
+{
+ struct osmo_smpp_acl *acl;
+
+ llist_for_each_entry(acl, &smsc->acl_list, list) {
+ if (!strcmp(acl->system_id, sys_id))
+ return acl;
+ }
+
+ return NULL;
+}
+
+struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id)
+{
+ struct osmo_smpp_acl *acl;
+
+ if (strlen(sys_id) > SMPP_SYS_ID_LEN)
+ return NULL;
+
+ if (smpp_acl_by_system_id(smsc, sys_id))
+ return NULL;
+
+ acl = talloc_zero(smsc, struct osmo_smpp_acl);
+ if (!acl)
+ return NULL;
+
+ acl->smsc = smsc;
+ strcpy(acl->system_id, sys_id);
+ INIT_LLIST_HEAD(&acl->route_list);
+
+ llist_add_tail(&acl->list, &smsc->acl_list);
+
+ return acl;
+}
+
+void smpp_acl_delete(struct osmo_smpp_acl *acl)
+{
+ struct osmo_smpp_route *r, *r2;
+
+ llist_del(&acl->list);
+
+ /* kill any active ESMEs */
+ if (acl->esme) {
+ struct osmo_esme *esme = acl->esme;
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ esme->wqueue.bfd.fd = -1;
+ esme->acl = NULL;
+ smpp_esme_put(esme);
+ }
+
+ /* delete all routes for this ACL */
+ llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
+ llist_del(&r->list);
+ llist_del(&r->global_list);
+ talloc_free(r);
+ }
+
+ talloc_free(acl);
+}
+
+static struct osmo_smpp_route *route_alloc(struct osmo_smpp_acl *acl)
+{
+ struct osmo_smpp_route *r;
+
+ r = talloc_zero(acl, struct osmo_smpp_route);
+ if (!r)
+ return NULL;
+
+ llist_add_tail(&r->list, &acl->route_list);
+ llist_add_tail(&r->global_list, &acl->smsc->route_list);
+
+ return r;
+}
+
+int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
+ const struct osmo_smpp_addr *pfx)
+{
+ struct osmo_smpp_route *r;
+
+ llist_for_each_entry(r, &acl->route_list, list) {
+ if (r->type == SMPP_ROUTE_PREFIX &&
+ smpp_addr_eq(&r->u.prefix, pfx))
+ return -EEXIST;
+ }
+
+ r = route_alloc(acl);
+ if (!r)
+ return -ENOMEM;
+ r->type = SMPP_ROUTE_PREFIX;
+ r->acl = acl;
+ memcpy(&r->u.prefix, pfx, sizeof(r->u.prefix));
+
+ return 0;
+}
+
+int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
+ const struct osmo_smpp_addr *pfx)
+{
+ struct osmo_smpp_route *r, *r2;
+
+ llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
+ if (r->type == SMPP_ROUTE_PREFIX &&
+ smpp_addr_eq(&r->u.prefix, pfx)) {
+ llist_del(&r->list);
+ talloc_free(r);
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
+
+/*! \brief increaes the use/reference count */
+void smpp_esme_get(struct osmo_esme *esme)
+{
+ esme->use++;
+}
+
+static void esme_destroy(struct osmo_esme *esme)
+{
+ osmo_wqueue_clear(&esme->wqueue);
+ if (esme->wqueue.bfd.fd >= 0) {
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ }
+ smpp_cmd_flush_pending(esme);
+ llist_del(&esme->list);
+ talloc_free(esme);
+}
+
+static uint32_t esme_inc_seq_nr(struct osmo_esme *esme)
+{
+ esme->own_seq_nr++;
+ if (esme->own_seq_nr > 0x7fffffff)
+ esme->own_seq_nr = 1;
+
+ return esme->own_seq_nr;
+}
+
+/*! \brief decrease the use/reference count, free if it is 0 */
+void smpp_esme_put(struct osmo_esme *esme)
+{
+ esme->use--;
+ if (esme->use <= 0)
+ esme_destroy(esme);
+}
+
+/*! \brief try to find a SMPP route (ESME) for given destination */
+struct osmo_esme *
+smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest)
+{
+ struct osmo_smpp_route *r;
+ struct osmo_smpp_acl *acl = NULL;
+
+ DEBUGP(DSMPP, "Looking up route for (%u/%u/%s)\n",
+ dest->ton, dest->npi, dest->addr);
+
+ /* search for a specific route */
+ llist_for_each_entry(r, &smsc->route_list, global_list) {
+ switch (r->type) {
+ case SMPP_ROUTE_PREFIX:
+ DEBUGP(DSMPP, "Checking prefix route (%u/%u/%s)->%s\n",
+ r->u.prefix.ton, r->u.prefix.npi, r->u.prefix.addr,
+ r->acl->system_id);
+ if (r->u.prefix.ton == dest->ton &&
+ r->u.prefix.npi == dest->npi &&
+ !strncmp(r->u.prefix.addr, dest->addr,
+ strlen(r->u.prefix.addr))) {
+ DEBUGP(DSMPP, "Found prefix route ACL\n");
+ acl = r->acl;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (acl)
+ break;
+ }
+
+ if (!acl) {
+ /* check for default route */
+ if (smsc->def_route) {
+ DEBUGP(DSMPP, "Using existing default route\n");
+ acl = smsc->def_route;
+ }
+ }
+
+ if (acl && acl->esme) {
+ struct osmo_esme *esme;
+ DEBUGP(DSMPP, "ACL even has ESME, we can route to it!\n");
+ esme = acl->esme;
+ if (esme->bind_flags & ESME_BIND_RX)
+ return esme;
+ else
+ LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, "
+ "but not bound for Rx, discarding MO SMS\n",
+ esme->system_id);
+ }
+
+ return NULL;
+}
+
+
+/*! \brief initialize the libsmpp34 data structure for a response */
+#define INIT_RESP(type, resp, req) { \
+ memset((resp), 0, sizeof(*(resp))); \
+ (resp)->command_length = 0; \
+ (resp)->command_id = type; \
+ (resp)->command_status = ESME_ROK; \
+ (resp)->sequence_number = (req)->sequence_number; \
+}
+
+/*! \brief pack a libsmpp34 data strcutrure and send it to the ESME */
+#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr)
+static int pack_and_send(struct osmo_esme *esme, uint32_t type, void *ptr)
+{
+ struct msgb *msg = msgb_alloc(4096, "SMPP_Tx");
+ int rc, rlen;
+ if (!msg)
+ return -ENOMEM;
+
+ rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr);
+ if (rc != 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n",
+ esme->system_id, smpp34_strerror);
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ msgb_put(msg, rlen);
+
+ if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n",
+ esme->system_id);
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/*! \brief transmit a generic NACK to a remote ESME */
+static int smpp_tx_gen_nack(struct osmo_esme *esme, uint32_t seq, uint32_t status)
+{
+ struct generic_nack_t nack;
+ char buf[SMALL_BUFF];
+
+ nack.command_length = 0;
+ nack.command_id = GENERIC_NACK;
+ nack.sequence_number = seq;
+ nack.command_status = status;
+
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Tx GENERIC NACK: %s\n",
+ esme->system_id, str_command_status(status, buf));
+
+ return PACK_AND_SEND(esme, &nack);
+}
+
+/*! \brief retrieve SMPP command ID from a msgb */
+static inline uint32_t smpp_msgb_cmdid(struct msgb *msg)
+{
+ uint8_t *tmp = msgb_data(msg) + 4;
+ return ntohl(*(uint32_t *)tmp);
+}
+
+/*! \brief retrieve SMPP sequence number from a msgb */
+static inline uint32_t smpp_msgb_seq(struct msgb *msg)
+{
+ uint8_t *tmp = msgb_data(msg);
+ return ntohl(*(uint32_t *)tmp);
+}
+
+/*! \brief handle an incoming SMPP generic NACK */
+static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct generic_nack_t nack;
+ char buf[SMALL_BUFF];
+ int rc;
+
+ SMPP34_UNPACK(rc, GENERIC_NACK, &nack, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Rx GENERIC NACK: %s\n",
+ esme->system_id, str_command_status(nack.command_status, buf));
+
+ return 0;
+}
+
+static int _process_bind(struct osmo_esme *esme, uint8_t if_version,
+ uint32_t bind_flags, const char *sys_id,
+ const char *passwd)
+{
+ struct osmo_smpp_acl *acl;
+
+ if (if_version != SMPP_VERSION)
+ return ESME_RSYSERR;
+
+ if (esme->bind_flags)
+ return ESME_RALYBND;
+
+ esme->smpp_version = if_version;
+ snprintf(esme->system_id, sizeof(esme->system_id), "%s", sys_id);
+
+ acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
+ if (!esme->smsc->accept_all) {
+ if (!acl) {
+ /* This system is unknown */
+ return ESME_RINVSYSID;
+ } else {
+ if (strlen(acl->passwd) &&
+ strcmp(acl->passwd, passwd)) {
+ return ESME_RINVPASWD;
+ }
+ }
+ }
+ if (acl) {
+ esme->acl = acl;
+ acl->esme = esme;
+ }
+
+ esme->bind_flags = bind_flags;
+
+ return ESME_ROK;
+}
+
+
+/*! \brief handle an incoming SMPP BIND RECEIVER */
+static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct bind_receiver_t bind;
+ struct bind_receiver_resp_t bind_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Rx from (Version %02x)\n",
+ bind.system_id, bind.interface_version);
+
+ rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX,
+ (const char *)bind.system_id, (const char *)bind.password);
+ bind_r.command_status = rc;
+
+ return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP BIND TRANSMITTER */
+static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct bind_transmitter_t bind;
+ struct bind_transmitter_resp_t bind_r;
+ struct tlv_t tlv;
+ int rc;
+
+ SMPP34_UNPACK(rc, BIND_TRANSMITTER, &bind, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Tx (Version %02x)\n",
+ bind.system_id, bind.interface_version);
+
+ rc = _process_bind(esme, bind.interface_version, ESME_BIND_TX,
+ (const char *)bind.system_id, (const char *)bind.password);
+ bind_r.command_status = rc;
+
+ /* build response */
+ snprintf((char *)bind_r.system_id, sizeof(bind_r.system_id), "%s",
+ esme->smsc->system_id);
+
+ /* add interface version TLV */
+ tlv.tag = TLVID_sc_interface_version;
+ tlv.length = sizeof(uint8_t);
+ tlv.value.val16 = esme->smpp_version;
+ build_tlv(&bind_r.tlv, &tlv);
+
+ return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP BIND TRANSCEIVER */
+static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct bind_transceiver_t bind;
+ struct bind_transceiver_resp_t bind_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ INIT_RESP(BIND_TRANSCEIVER_RESP, &bind_r, &bind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Trx (Version %02x)\n",
+ bind.system_id, bind.interface_version);
+
+ rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX|ESME_BIND_TX,
+ (const char *)bind.system_id, (const char *)bind.password);
+ bind_r.command_status = rc;
+
+ return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP UNBIND */
+static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct unbind_t unbind;
+ struct unbind_resp_t unbind_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, UNBIND, &unbind, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ INIT_RESP(UNBIND_RESP, &unbind_r, &unbind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx UNBIND\n", esme->system_id);
+
+ if (esme->bind_flags == 0) {
+ unbind_r.command_status = ESME_RINVBNDSTS;
+ goto err;
+ }
+
+ esme->bind_flags = 0;
+err:
+ return PACK_AND_SEND(esme, &unbind_r);
+}
+
+/*! \brief handle an incoming SMPP ENQUIRE LINK */
+static int smpp_handle_enq_link(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct enquire_link_t enq;
+ struct enquire_link_resp_t enq_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, ENQUIRE_LINK, &enq, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] Rx Enquire Link\n", esme->system_id);
+
+ INIT_RESP(ENQUIRE_LINK_RESP, &enq_r, &enq);
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx Enquire Link Response\n", esme->system_id);
+
+ return PACK_AND_SEND(esme, &enq_r);
+}
+
+/*! \brief send a SUBMIT-SM RESPONSE to a remote ESME */
+int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr,
+ uint32_t command_status, char *msg_id)
+{
+ struct submit_sm_resp_t submit_r;
+
+ memset(&submit_r, 0, sizeof(submit_r));
+ submit_r.command_length = 0;
+ submit_r.command_id = SUBMIT_SM_RESP;
+ submit_r.command_status = command_status;
+ submit_r.sequence_number= sequence_nr;
+ snprintf((char *) submit_r.message_id, sizeof(submit_r.message_id), "%s", msg_id);
+
+ return PACK_AND_SEND(esme, &submit_r);
+}
+
+static const struct value_string smpp_avail_strs[] = {
+ { 0, "Available" },
+ { 1, "Denied" },
+ { 2, "Unavailable" },
+ { 0, NULL }
+};
+
+/*! \brief send an ALERT_NOTIFICATION to a remote ESME */
+int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
+ const char *addr, uint8_t avail_status)
+{
+ struct alert_notification_t alert;
+ struct tlv_t tlv;
+
+ memset(&alert, 0, sizeof(alert));
+ alert.command_length = 0;
+ alert.command_id = ALERT_NOTIFICATION;
+ alert.command_status = ESME_ROK;
+ alert.sequence_number = esme_inc_seq_nr(esme);
+ alert.source_addr_ton = ton;
+ alert.source_addr_npi = npi;
+ snprintf((char *)alert.source_addr, sizeof(alert.source_addr), "%s", addr);
+
+ tlv.tag = TLVID_ms_availability_status;
+ tlv.length = sizeof(uint8_t);
+ tlv.value.val08 = avail_status;
+ build_tlv(&alert.tlv, &tlv);
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx ALERT_NOTIFICATION (%s/%u/%u): %s\n",
+ esme->system_id, alert.source_addr, alert.source_addr_ton,
+ alert.source_addr_npi,
+ get_value_string(smpp_avail_strs, avail_status));
+
+ return PACK_AND_SEND(esme, &alert);
+}
+
+/* \brief send a DELIVER-SM message to given ESME */
+int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver)
+{
+ deliver->sequence_number = esme_inc_seq_nr(esme);
+
+ return PACK_AND_SEND(esme, deliver);
+}
+
+/*! \brief handle an incoming SMPP DELIVER-SM RESPONSE */
+static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct deliver_sm_resp_t deliver_r;
+ struct osmo_smpp_cmd *cmd;
+ int rc;
+
+ memset(&deliver_r, 0, sizeof(deliver_r));
+ SMPP34_UNPACK(rc, DELIVER_SM_RESP, &deliver_r, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ cmd = smpp_cmd_find_by_seqnum(esme, deliver_r.sequence_number);
+ if (!cmd) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Rx DELIVER-SM RESP !? (%s)\n",
+ esme->system_id, get_value_string(smpp_status_strs,
+ deliver_r.command_status));
+ return -1;
+ }
+
+ if (deliver_r.command_status == ESME_ROK)
+ smpp_cmd_ack(cmd);
+ else
+ smpp_cmd_err(cmd, deliver_r.command_status);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx DELIVER-SM RESP (%s)\n",
+ esme->system_id, get_value_string(smpp_status_strs,
+ deliver_r.command_status));
+
+ return 0;
+}
+
+/*! \brief handle an incoming SMPP SUBMIT-SM */
+static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct submit_sm_t submit;
+ struct submit_sm_resp_t submit_r;
+ int rc;
+
+ memset(&submit, 0, sizeof(submit));
+ SMPP34_UNPACK(rc, SUBMIT_SM, &submit, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+ esme->system_id, smpp34_strerror);
+ return rc;
+ }
+
+ INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit);
+
+ if (!(esme->bind_flags & ESME_BIND_TX)) {
+ submit_r.command_status = ESME_RINVBNDSTS;
+ return PACK_AND_SEND(esme, &submit_r);
+ }
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx SUBMIT-SM (%s/%u/%u)\n",
+ esme->system_id, submit.destination_addr,
+ submit.dest_addr_ton, submit.dest_addr_npi);
+
+ INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit);
+
+ rc = handle_smpp_submit(esme, &submit, &submit_r);
+ if (rc == 0)
+ return PACK_AND_SEND(esme, &submit_r);
+
+ return rc;
+}
+
+/*! \brief one complete SMPP PDU from the ESME has been received */
+static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses)
+{
+ uint32_t cmd_id = smpp_msgb_cmdid(msg);
+ int rc = 0;
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] smpp_pdu_rx(%s)\n", esme->system_id,
+ osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ switch (cmd_id) {
+ case GENERIC_NACK:
+ rc = smpp_handle_gen_nack(esme, msg);
+ break;
+ case BIND_RECEIVER:
+ rc = smpp_handle_bind_rx(esme, msg);
+ break;
+ case BIND_TRANSMITTER:
+ rc = smpp_handle_bind_tx(esme, msg);
+ break;
+ case BIND_TRANSCEIVER:
+ rc = smpp_handle_bind_trx(esme, msg);
+ break;
+ case UNBIND:
+ rc = smpp_handle_unbind(esme, msg);
+ break;
+ case ENQUIRE_LINK:
+ rc = smpp_handle_enq_link(esme, msg);
+ break;
+ case SUBMIT_SM:
+ rc = smpp_handle_submit(esme, msg);
+ break;
+ case DELIVER_SM_RESP:
+ rc = smpp_handle_deliver_resp(esme, msg);
+ break;
+ case DELIVER_SM:
+ break;
+ case DATA_SM:
+ break;
+ case CANCEL_SM:
+ case QUERY_SM:
+ case REPLACE_SM:
+ case SUBMIT_MULTI:
+ LOGP(DSMPP, LOGL_NOTICE, "[%s] Unimplemented PDU Command "
+ "0x%08x\n", esme->system_id, cmd_id);
+ break;
+ default:
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Unknown PDU Command 0x%08x\n",
+ esme->system_id, cmd_id);
+ rc = smpp_tx_gen_nack(esme, smpp_msgb_seq(msg), ESME_RINVCMDID);
+ break;
+ }
+
+ return rc;
+}
+
+/* This macro should be called after a call to read() in the read_cb of an
+ * osmo_fd to properly check for errors.
+ * rc is the return value of read, err_label is the label to jump to in case of
+ * an error. The code there should handle closing the connection.
+ * FIXME: This code should go in libosmocore utils.h so it can be used by other
+ * projects as well.
+ * */
+#define OSMO_FD_CHECK_READ(rc, err_label) \
+ if (rc < 0) { \
+ /* EINTR is a non-fatal error, just try again */ \
+ if (errno == EINTR) \
+ return 0; \
+ goto err_label; \
+ } else if (rc == 0) { \
+ goto err_label; \
+ }
+
+/* !\brief call-back when per-ESME TCP socket has some data to be read */
+static int esme_link_read_cb(struct osmo_fd *ofd)
+{
+ struct osmo_esme *esme = ofd->data;
+ uint32_t len;
+ uint8_t *lenptr = (uint8_t *) &len;
+ uint8_t *cur;
+ struct msgb *msg;
+ ssize_t rdlen, rc;
+
+ switch (esme->read_state) {
+ case READ_ST_IN_LEN:
+ rdlen = sizeof(uint32_t) - esme->read_idx;
+ rc = read(ofd->fd, lenptr + esme->read_idx, rdlen);
+ if (rc < 0)
+ LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n",
+ esme->system_id, rc, strerror(errno));
+ OSMO_FD_CHECK_READ(rc, dead_socket);
+
+ esme->read_idx += rc;
+
+ if (esme->read_idx >= sizeof(uint32_t)) {
+ esme->read_len = ntohl(len);
+ if (esme->read_len < 8 || esme->read_len > UINT16_MAX) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] length invalid %u\n",
+ esme->system_id, esme->read_len);
+ goto dead_socket;
+ }
+
+ msg = msgb_alloc(esme->read_len, "SMPP Rx");
+ if (!msg)
+ return -ENOMEM;
+ esme->read_msg = msg;
+ cur = msgb_put(msg, sizeof(uint32_t));
+ memcpy(cur, lenptr, sizeof(uint32_t));
+ esme->read_state = READ_ST_IN_MSG;
+ esme->read_idx = sizeof(uint32_t);
+ }
+ break;
+ case READ_ST_IN_MSG:
+ msg = esme->read_msg;
+ rdlen = esme->read_len - esme->read_idx;
+ rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg)));
+ if (rc < 0)
+ LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n",
+ esme->system_id, rc, strerror(errno));
+ OSMO_FD_CHECK_READ(rc, dead_socket);
+
+ esme->read_idx += rc;
+ msgb_put(msg, rc);
+
+ if (esme->read_idx >= esme->read_len) {
+ rc = smpp_pdu_rx(esme, esme->read_msg);
+ msgb_free(esme->read_msg);
+ esme->read_msg = NULL;
+ esme->read_idx = 0;
+ esme->read_len = 0;
+ esme->read_state = READ_ST_IN_LEN;
+ }
+ break;
+ }
+
+ return 0;
+dead_socket:
+ msgb_free(esme->read_msg);
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ esme->wqueue.bfd.fd = -1;
+ smpp_esme_put(esme);
+
+ return 0;
+}
+
+/* call-back of write queue once it wishes to write a message to the socket */
+static int esme_link_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ struct osmo_esme *esme = ofd->data;
+ int rc;
+
+ rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc == 0) {
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ esme->wqueue.bfd.fd = -1;
+ smpp_esme_put(esme);
+ } else if (rc < msgb_length(msg)) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* callback for already-accepted new TCP socket */
+static int link_accept_cb(struct smsc *smsc, int fd,
+ struct sockaddr_storage *s, socklen_t s_len)
+{
+ struct osmo_esme *esme = talloc_zero(smsc, struct osmo_esme);
+ if (!esme) {
+ close(fd);
+ return -ENOMEM;
+ }
+
+ INIT_LLIST_HEAD(&esme->smpp_cmd_list);
+ smpp_esme_get(esme);
+ esme->own_seq_nr = rand();
+ esme_inc_seq_nr(esme);
+ esme->smsc = smsc;
+ osmo_wqueue_init(&esme->wqueue, 10);
+ esme->wqueue.bfd.fd = fd;
+ esme->wqueue.bfd.data = esme;
+ esme->wqueue.bfd.when = BSC_FD_READ;
+
+ if (osmo_fd_register(&esme->wqueue.bfd) != 0) {
+ close(fd);
+ talloc_free(esme);
+ return -EIO;
+ }
+
+ esme->wqueue.read_cb = esme_link_read_cb;
+ esme->wqueue.write_cb = esme_link_write_cb;
+
+ esme->sa_len = OSMO_MIN(sizeof(esme->sa), s_len);
+ memcpy(&esme->sa, s, esme->sa_len);
+
+ llist_add_tail(&esme->list, &smsc->esme_list);
+
+ return 0;
+}
+
+/* callback of listening TCP socket */
+static int smsc_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int rc;
+ struct sockaddr_storage sa;
+ socklen_t sa_len = sizeof(sa);
+
+ rc = accept(ofd->fd, (struct sockaddr *)&sa, &sa_len);
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "Accept returns %d (%s)\n",
+ rc, strerror(errno));
+ return rc;
+ }
+ return link_accept_cb(ofd->data, rc, &sa, sa_len);
+}
+
+/*! \brief allocate and initialize an smsc struct from talloc context ctx. */
+struct smsc *smpp_smsc_alloc_init(void *ctx)
+{
+ struct smsc *smsc = talloc_zero(ctx, struct smsc);
+
+ INIT_LLIST_HEAD(&smsc->esme_list);
+ INIT_LLIST_HEAD(&smsc->acl_list);
+ INIT_LLIST_HEAD(&smsc->route_list);
+
+ smsc->listen_ofd.data = smsc;
+ smsc->listen_ofd.cb = smsc_fd_cb;
+
+ return smsc;
+}
+
+/*! \brief Set the SMPP address and port without binding. */
+int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+ talloc_free((void*)smsc->bind_addr);
+ smsc->bind_addr = NULL;
+ if (bind_addr) {
+ smsc->bind_addr = talloc_strdup(smsc, bind_addr);
+ if (!smsc->bind_addr)
+ return -ENOMEM;
+ }
+ smsc->listen_port = port;
+ return 0;
+}
+
+/*! \brief Bind to given address and port and accept connections.
+ * \param[in] bind_addr Local IP address, may be NULL for any.
+ * \param[in] port TCP port number, may be 0 for default SMPP (2775).
+ */
+int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+ int rc;
+
+ /* default port for SMPP */
+ if (!port)
+ port = 2775;
+
+ smpp_smsc_stop(smsc);
+
+ LOGP(DSMPP, LOGL_NOTICE, "SMPP at %s %d\n",
+ bind_addr? bind_addr : "0.0.0.0", port);
+
+ rc = osmo_sock_init_ofd(&smsc->listen_ofd, AF_UNSPEC, SOCK_STREAM,
+ IPPROTO_TCP, bind_addr, port,
+ OSMO_SOCK_F_BIND);
+ if (rc < 0)
+ return rc;
+
+ /* store new address and port */
+ rc = smpp_smsc_conf(smsc, bind_addr, port);
+ if (rc)
+ smpp_smsc_stop(smsc);
+ return rc;
+}
+
+/*! \brief Change a running connection to a different address/port, and upon
+ * error switch back to the running configuration. */
+int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+ int rc;
+
+ rc = smpp_smsc_start(smsc, bind_addr, port);
+ if (rc)
+ /* if there is an error, try to re-bind to the old port */
+ return smpp_smsc_start(smsc, smsc->bind_addr, smsc->listen_port);
+ return 0;
+}
+
+/*! /brief Close SMPP connection. */
+void smpp_smsc_stop(struct smsc *smsc)
+{
+ if (smsc->listen_ofd.fd > 0) {
+ close(smsc->listen_ofd.fd);
+ smsc->listen_ofd.fd = 0;
+ osmo_fd_unregister(&smsc->listen_ofd);
+ }
+}
diff --git a/src/libmsc/smpp_smsc.h b/src/libmsc/smpp_smsc.h
new file mode 100644
index 000000000..d8e82e421
--- /dev/null
+++ b/src/libmsc/smpp_smsc.h
@@ -0,0 +1,166 @@
+#ifndef _SMPP_SMSC_H
+#define _SMPP_SMSC_H
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#define SMPP_SYS_ID_LEN 16
+#define SMPP_PASSWD_LEN 16
+
+#define MODE_7BIT 7
+#define MODE_8BIT 8
+
+enum esme_read_state {
+ READ_ST_IN_LEN = 0,
+ READ_ST_IN_MSG = 1,
+};
+
+struct osmo_smpp_acl;
+
+struct osmo_smpp_addr {
+ uint8_t ton;
+ uint8_t npi;
+ char addr[21+1];
+};
+
+struct osmo_esme {
+ struct llist_head list;
+ struct smsc *smsc;
+ struct osmo_smpp_acl *acl;
+ int use;
+
+ struct llist_head smpp_cmd_list;
+
+ uint32_t own_seq_nr;
+
+ struct osmo_wqueue wqueue;
+ struct sockaddr_storage sa;
+ socklen_t sa_len;
+
+ enum esme_read_state read_state;
+ uint32_t read_len;
+ uint32_t read_idx;
+ struct msgb *read_msg;
+
+ uint8_t smpp_version;
+ char system_id[SMPP_SYS_ID_LEN+1];
+
+ uint8_t bind_flags;
+};
+
+struct osmo_smpp_acl {
+ struct llist_head list;
+ struct smsc *smsc;
+ struct osmo_esme *esme;
+ char *description;
+ char system_id[SMPP_SYS_ID_LEN+1];
+ char passwd[SMPP_PASSWD_LEN+1];
+ int default_route;
+ int deliver_src_imsi;
+ int osmocom_ext;
+ int dcs_transparent;
+ struct llist_head route_list;
+};
+
+enum osmo_smpp_rtype {
+ SMPP_ROUTE_NONE,
+ SMPP_ROUTE_PREFIX,
+};
+
+struct osmo_smpp_route {
+ struct llist_head list; /*!< in acl.route_list */
+ struct llist_head global_list; /*!< in smsc->route_list */
+ struct osmo_smpp_acl *acl;
+ enum osmo_smpp_rtype type;
+ union {
+ struct osmo_smpp_addr prefix;
+ } u;
+};
+
+struct osmo_smpp_cmd {
+ struct llist_head list;
+ struct gsm_subscriber *subscr;
+ struct gsm_sms *sms;
+ uint32_t sequence_nr;
+ struct osmo_timer_list response_timer;
+};
+
+struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme,
+ uint32_t sequence_number);
+void smpp_cmd_ack(struct osmo_smpp_cmd *cmd);
+void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status);
+void smpp_cmd_flush_pending(struct osmo_esme *esme);
+
+struct smsc {
+ struct osmo_fd listen_ofd;
+ struct llist_head esme_list;
+ struct llist_head acl_list;
+ struct llist_head route_list;
+ const char *bind_addr;
+ uint16_t listen_port;
+ char system_id[SMPP_SYS_ID_LEN+1];
+ int accept_all;
+ int smpp_first;
+ struct osmo_smpp_acl *def_route;
+ void *priv;
+};
+
+int smpp_addr_eq(const struct osmo_smpp_addr *a,
+ const struct osmo_smpp_addr *b);
+
+struct smsc *smpp_smsc_alloc_init(void *ctx);
+int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port);
+int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port);
+int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port);
+void smpp_smsc_stop(struct smsc *smsc);
+
+void smpp_esme_get(struct osmo_esme *esme);
+void smpp_esme_put(struct osmo_esme *esme);
+
+struct osmo_esme *
+smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest);
+
+struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id);
+struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
+ const char *sys_id);
+void smpp_acl_delete(struct osmo_smpp_acl *acl);
+
+int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr,
+ uint32_t command_status, char *msg_id);
+
+int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
+ const char *addr, uint8_t avail_status);
+
+int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver);
+
+int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
+ struct submit_sm_resp_t *submit_r);
+
+int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
+ const struct osmo_smpp_addr *pfx);
+int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
+ const struct osmo_smpp_addr *pfx);
+
+int smpp_vty_init(void);
+
+int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode);
+
+
+
+struct gsm_sms;
+struct gsm_subscriber_connection;
+
+int smpp_route_smpp_first(struct gsm_sms *sms,
+ struct gsm_subscriber_connection *conn);
+int smpp_try_deliver(struct gsm_sms *sms,
+ struct gsm_subscriber_connection *conn, bool *deferred);
+#endif
diff --git a/src/libmsc/smpp_utils.c b/src/libmsc/smpp_utils.c
new file mode 100644
index 000000000..d0850d8c1
--- /dev/null
+++ b/src/libmsc/smpp_utils.c
@@ -0,0 +1,62 @@
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "smpp_smsc.h"
+#include <openbsc/debug.h>
+
+
+int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode)
+{
+ if ((dcs & 0xF0) == 0xF0) {
+ if (dcs & 0x04) {
+ /* bit 2 == 1: 8bit data */
+ *data_coding = 0x02;
+ *mode = MODE_8BIT;
+ } else {
+ /* bit 2 == 0: default alphabet */
+ *data_coding = 0x01;
+ *mode = MODE_7BIT;
+ }
+ } else if ((dcs & 0xE0) == 0) {
+ switch (dcs & 0xC) {
+ case 0:
+ *data_coding = 0x01;
+ *mode = MODE_7BIT;
+ break;
+ case 4:
+ *data_coding = 0x02;
+ *mode = MODE_8BIT;
+ break;
+ case 8:
+ *data_coding = 0x08; /* UCS-2 */
+ *mode = MODE_8BIT;
+ break;
+ default:
+ goto unknown_mo;
+ }
+ } else {
+unknown_mo:
+ LOGP(DLSMS, LOGL_ERROR, "SMPP MO Unknown Data Coding 0x%02x\n", dcs);
+ return -1;
+ }
+
+ return 0;
+
+}
diff --git a/src/libmsc/smpp_vty.c b/src/libmsc/smpp_vty.c
new file mode 100644
index 000000000..13467f182
--- /dev/null
+++ b/src/libmsc/smpp_vty.c
@@ -0,0 +1,612 @@
+/* SMPP vty interface */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+
+#include <openbsc/vty.h>
+
+#include "smpp_smsc.h"
+
+struct smsc *smsc_from_vty(struct vty *v);
+
+static struct cmd_node smpp_node = {
+ SMPP_NODE,
+ "%s(config-smpp)# ",
+ 1,
+};
+
+static struct cmd_node esme_node = {
+ SMPP_ESME_NODE,
+ "%s(config-smpp-esme)# ",
+ 1,
+};
+
+DEFUN(cfg_smpp, cfg_smpp_cmd,
+ "smpp", "Configure SMPP SMS Interface")
+{
+ vty->node = SMPP_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_first, cfg_smpp_first_cmd,
+ "smpp-first",
+ "Try SMPP routes before the subscriber DB\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ smsc->smpp_first = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_smpp_first, cfg_no_smpp_first_cmd,
+ "no smpp-first",
+ NO_STR "Try SMPP before routes before the subscriber DB\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ smsc->smpp_first = 0;
+ return CMD_SUCCESS;
+}
+
+static int smpp_local_tcp(struct vty *vty,
+ const char *bind_addr, uint16_t port)
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ int is_running = smsc->listen_ofd.fd > 0;
+ int same_bind_addr;
+ int rc;
+
+ /* If it is not up yet, don't rebind, just set values. */
+ if (!is_running) {
+ rc = smpp_smsc_conf(smsc, bind_addr, port);
+ if (rc < 0) {
+ vty_out(vty, "%% Cannot configure new address:port%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+ }
+
+ rc = smpp_smsc_restart(smsc, bind_addr, port);
+ if (rc < 0) {
+ vty_out(vty, "%% Cannot bind to new port %s:%u nor to"
+ " old port %s:%u%s",
+ bind_addr? bind_addr : "0.0.0.0",
+ port,
+ smsc->bind_addr? smsc->bind_addr : "0.0.0.0",
+ smsc->listen_port,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ same_bind_addr = (bind_addr == smsc->bind_addr)
+ || (bind_addr && smsc->bind_addr
+ && (strcmp(bind_addr, smsc->bind_addr) == 0));
+
+ if (!same_bind_addr || port != smsc->listen_port) {
+ vty_out(vty, "%% Cannot bind to new port %s:%u, staying on"
+ " old port %s:%u%s",
+ bind_addr? bind_addr : "0.0.0.0",
+ port,
+ smsc->bind_addr? smsc->bind_addr : "0.0.0.0",
+ smsc->listen_port,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_port, cfg_smpp_port_cmd,
+ "local-tcp-port <1-65535>",
+ "Set the local TCP port on which we listen for SMPP\n"
+ "TCP port number")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ uint16_t port = atoi(argv[0]);
+ return smpp_local_tcp(vty, smsc->bind_addr, port);
+}
+
+DEFUN(cfg_smpp_addr_port, cfg_smpp_addr_port_cmd,
+ "local-tcp-ip A.B.C.D <1-65535>",
+ "Set the local IP address and TCP port on which we listen for SMPP\n"
+ "Local IP address\n"
+ "TCP port number")
+{
+ const char *bind_addr = argv[0];
+ uint16_t port = atoi(argv[1]);
+ return smpp_local_tcp(vty, bind_addr, port);
+}
+
+DEFUN(cfg_smpp_sys_id, cfg_smpp_sys_id_cmd,
+ "system-id ID",
+ "Set the System ID of this SMSC\n"
+ "Alphanumeric SMSC System ID\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+
+ if (strlen(argv[0])+1 > sizeof(smsc->system_id))
+ return CMD_WARNING;
+
+ strcpy(smsc->system_id, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_policy, cfg_smpp_policy_cmd,
+ "policy (accept-all|closed)",
+ "Set the authentication policy of this SMSC\n"
+ "Accept all SMPP connections independeint of system ID / passwd\n"
+ "Accept only SMPP connections from ESMEs explicitly configured")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+
+ if (!strcmp(argv[0], "accept-all"))
+ smsc->accept_all = 1;
+ else
+ smsc->accept_all = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+static int config_write_smpp(struct vty *vty)
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+
+ vty_out(vty, "smpp%s", VTY_NEWLINE);
+ if (smsc->bind_addr)
+ vty_out(vty, " local-tcp-ip %s %u%s", smsc->bind_addr,
+ smsc->listen_port, VTY_NEWLINE);
+ else
+ vty_out(vty, " local-tcp-port %u%s", smsc->listen_port,
+ VTY_NEWLINE);
+ if (strlen(smsc->system_id) > 0)
+ vty_out(vty, " system-id %s%s", smsc->system_id, VTY_NEWLINE);
+ vty_out(vty, " policy %s%s",
+ smsc->accept_all ? "accept-all" : "closed", VTY_NEWLINE);
+ vty_out(vty, " %ssmpp-first%s",
+ smsc->smpp_first ? "" : "no ", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme, cfg_esme_cmd,
+ "esme NAME",
+ "Configure a particular ESME\n"
+ "Alphanumeric System ID of the ESME to be configured\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ struct osmo_smpp_acl *acl;
+ const char *id = argv[0];
+
+ if (strlen(id) > 16) {
+ vty_out(vty, "%% System ID cannot be more than 16 "
+ "characters long%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ acl = smpp_acl_by_system_id(smsc, id);
+ if (!acl) {
+ acl = smpp_acl_alloc(smsc, id);
+ if (!acl)
+ return CMD_WARNING;
+ }
+
+ vty->index = acl;
+ vty->index_sub = &acl->description;
+ vty->node = SMPP_ESME_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_esme, cfg_no_esme_cmd,
+ "no esme NAME",
+ NO_STR "Remove ESME configuration\n"
+ "Alphanumeric System ID of the ESME to be removed\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ struct osmo_smpp_acl *acl;
+ const char *id = argv[0];
+
+ acl = smpp_acl_by_system_id(smsc, id);
+ if (!acl) {
+ vty_out(vty, "%% ESME with system id '%s' unknown%s",
+ id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* FIXME: close the connection, free data structure, etc. */
+
+ smpp_acl_delete(acl);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_esme_passwd, cfg_esme_passwd_cmd,
+ "password PASSWORD",
+ "Set the password for this ESME\n"
+ "Alphanumeric password string\n")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ if (strlen(argv[0])+1 > sizeof(acl->passwd))
+ return CMD_WARNING;
+
+ strcpy(acl->passwd, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_passwd, cfg_esme_no_passwd_cmd,
+ "no password",
+ NO_STR "Remove the password for this ESME\n")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ memset(acl->passwd, 0, sizeof(acl->passwd));
+
+ return CMD_SUCCESS;
+}
+
+static int osmo_is_digits(const char *str)
+{
+ int i;
+ for (i = 0; i < strlen(str); i++) {
+ if (!isdigit(str[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static const struct value_string route_errstr[] = {
+ { -EEXIST, "Route already exists" },
+ { -ENODEV, "Route does not exist" },
+ { -ENOMEM, "No memory" },
+ { -EINVAL, "Invalid" },
+ { 0, NULL }
+};
+
+static const struct value_string smpp_ton_str_short[] = {
+ { TON_Unknown, "unknown" },
+ { TON_International, "international" },
+ { TON_National, "national" },
+ { TON_Network_Specific, "network" },
+ { TON_Subscriber_Number,"subscriber" },
+ { TON_Alphanumeric, "alpha" },
+ { TON_Abbreviated, "abbrev" },
+ { 0, NULL }
+};
+
+static const struct value_string smpp_npi_str_short[] = {
+ { NPI_Unknown, "unknown" },
+ { NPI_ISDN_E163_E164, "isdn" },
+ { NPI_Data_X121, "x121" },
+ { NPI_Telex_F69, "f69" },
+ { NPI_Land_Mobile_E212, "e212" },
+ { NPI_National, "national" },
+ { NPI_Private, "private" },
+ { NPI_ERMES, "ermes" },
+ { NPI_Internet_IP, "ip" },
+ { NPI_WAP_Client_Id, "wap" },
+ { 0, NULL }
+};
+
+
+#define SMPP_ROUTE_STR "Configure a route for MO-SMS to be sent to this ESME\n"
+#define SMPP_ROUTE_P_STR SMPP_ROUTE_STR "Prefix-match route\n"
+#define SMPP_PREFIX_STR "Destination number prefix\n"
+
+#define TON_CMD "(unknown|international|national|network|subscriber|alpha|abbrev)"
+#define NPI_CMD "(unknown|isdn|x121|f69|e212|national|private|ermes|ip|wap)"
+#define TON_STR "Unknown type-of-number\n" \
+ "International type-of-number\n" \
+ "National type-of-number\n" \
+ "Network specific type-of-number\n" \
+ "Subscriber type-of-number\n" \
+ "Alphanumeric type-of-number\n" \
+ "Abbreviated type-of-number\n"
+#define NPI_STR "Unknown numbering plan\n" \
+ "ISDN (E.164) numbering plan\n" \
+ "X.121 numbering plan\n" \
+ "F.69 numbering plan\n" \
+ "E.212 numbering plan\n" \
+ "National numbering plan\n" \
+ "Private numbering plan\n" \
+ "ERMES numbering plan\n" \
+ "IP numbering plan\n" \
+ "WAP numbeing plan\n"
+
+DEFUN(cfg_esme_route_pfx, cfg_esme_route_pfx_cmd,
+ "route prefix " TON_CMD " " NPI_CMD " PREFIX",
+ SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
+{
+ struct osmo_smpp_acl *acl = vty->index;
+ struct osmo_smpp_addr pfx;
+ int rc;
+
+ /* check if DESTINATION is all-digits */
+ if (!osmo_is_digits(argv[2])) {
+ vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
+ pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
+ snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
+
+ rc = smpp_route_pfx_add(acl, &pfx);
+ if (rc < 0) {
+ vty_out(vty, "%% error adding prefix route: %s%s",
+ get_value_string(route_errstr, rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_route_pfx, cfg_esme_no_route_pfx_cmd,
+ "no route prefix " TON_CMD " " NPI_CMD " PREFIX",
+ NO_STR SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
+{
+ struct osmo_smpp_acl *acl = vty->index;
+ struct osmo_smpp_addr pfx;
+ int rc;
+
+ /* check if DESTINATION is all-digits */
+ if (!osmo_is_digits(argv[2])) {
+ vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
+ pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
+ snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
+
+ rc = smpp_route_pfx_del(acl, &pfx);
+ if (rc < 0) {
+ vty_out(vty, "%% error removing prefix route: %s%s",
+ get_value_string(route_errstr, rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+
+DEFUN(cfg_esme_defaultroute, cfg_esme_defaultroute_cmd,
+ "default-route",
+ "Set this ESME as default-route for all SMS to unknown destinations")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->default_route = 1;
+
+ if (!acl->smsc->def_route)
+ acl->smsc->def_route = acl;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_esme_defaultroute, cfg_esme_no_defaultroute_cmd,
+ "no default-route", NO_STR
+ "Remove this ESME as default-route for all SMS to unknown destinations")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->default_route = 0;
+
+ /* remove currently active default route, if it was created by
+ * this ACL */
+ if (acl->smsc->def_route && acl->smsc->def_route == acl)
+ acl->smsc->def_route = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_del_src_imsi, cfg_esme_del_src_imsi_cmd,
+ "deliver-src-imsi",
+ "Enable the use of IMSI as source address in DELIVER")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->deliver_src_imsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_del_src_imsi, cfg_esme_no_del_src_imsi_cmd,
+ "no deliver-src-imsi", NO_STR
+ "Disable the use of IMSI as source address in DELIVER")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->deliver_src_imsi = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_osmo_ext, cfg_esme_osmo_ext_cmd,
+ "osmocom-extensions",
+ "Enable the use of Osmocom SMPP Extensions for this ESME")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->osmocom_ext = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_osmo_ext, cfg_esme_no_osmo_ext_cmd,
+ "no osmocom-extensions", NO_STR
+ "Disable the use of Osmocom SMPP Extensions for this ESME")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->osmocom_ext = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_dcs_transp, cfg_esme_dcs_transp_cmd,
+ "dcs-transparent",
+ "Enable the transparent pass-through of TP-DCS to SMPP DataCoding")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->dcs_transparent = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_dcs_transp, cfg_esme_no_dcs_transp_cmd,
+ "no dcs-transparent", NO_STR
+ "Disable the transparent pass-through of TP-DCS to SMPP DataCoding")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->dcs_transparent = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+static void dump_one_esme(struct vty *vty, struct osmo_esme *esme)
+{
+ char host[128], serv[128];
+
+ host[0] = 0;
+ serv[0] = 0;
+ getnameinfo((const struct sockaddr *) &esme->sa, esme->sa_len,
+ host, sizeof(host), serv, sizeof(serv), NI_NUMERICSERV);
+
+ vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s",
+ esme->system_id, esme->acl ? esme->acl->passwd : "",
+ esme->smpp_version, VTY_NEWLINE);
+ vty_out(vty, " Connected from: %s:%s%s", host, serv, VTY_NEWLINE);
+ if (esme->smsc->def_route == esme->acl)
+ vty_out(vty, " Is current default route%s", VTY_NEWLINE);
+}
+
+DEFUN(show_esme, show_esme_cmd,
+ "show smpp esme",
+ SHOW_STR "SMPP Interface\n" "SMPP Extrenal SMS Entity\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ struct osmo_esme *esme;
+
+ llist_for_each_entry(esme, &smsc->esme_list, list)
+ dump_one_esme(vty, esme);
+
+ return CMD_SUCCESS;
+}
+
+static void write_esme_route_single(struct vty *vty, struct osmo_smpp_route *r)
+{
+ switch (r->type) {
+ case SMPP_ROUTE_PREFIX:
+ vty_out(vty, " route prefix %s ",
+ get_value_string(smpp_ton_str_short, r->u.prefix.ton));
+ vty_out(vty, "%s %s%s",
+ get_value_string(smpp_npi_str_short, r->u.prefix.npi),
+ r->u.prefix.addr, VTY_NEWLINE);
+ break;
+ case SMPP_ROUTE_NONE:
+ break;
+ }
+}
+
+static void config_write_esme_single(struct vty *vty, struct osmo_smpp_acl *acl)
+{
+ struct osmo_smpp_route *r;
+
+ vty_out(vty, " esme %s%s", acl->system_id, VTY_NEWLINE);
+ if (strlen(acl->passwd))
+ vty_out(vty, " password %s%s", acl->passwd, VTY_NEWLINE);
+ if (acl->default_route)
+ vty_out(vty, " default-route%s", VTY_NEWLINE);
+ if (acl->deliver_src_imsi)
+ vty_out(vty, " deliver-src-imsi%s", VTY_NEWLINE);
+ if (acl->osmocom_ext)
+ vty_out(vty, " osmocom-extensions%s", VTY_NEWLINE);
+ if (acl->dcs_transparent)
+ vty_out(vty, " dcs-transparent%s", VTY_NEWLINE);
+
+ llist_for_each_entry(r, &acl->route_list, list)
+ write_esme_route_single(vty, r);
+}
+
+static int config_write_esme(struct vty *v)
+{
+ struct smsc *smsc = smsc_from_vty(v);
+ struct osmo_smpp_acl *acl;
+
+ llist_for_each_entry(acl, &smsc->acl_list, list)
+ config_write_esme_single(v, acl);
+
+ return CMD_SUCCESS;
+}
+
+int smpp_vty_init(void)
+{
+ install_node(&smpp_node, config_write_smpp);
+ vty_install_default(SMPP_NODE);
+ install_element(CONFIG_NODE, &cfg_smpp_cmd);
+
+ install_element(SMPP_NODE, &cfg_smpp_first_cmd);
+ install_element(SMPP_NODE, &cfg_no_smpp_first_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_port_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_addr_port_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_sys_id_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_policy_cmd);
+ install_element(SMPP_NODE, &cfg_esme_cmd);
+ install_element(SMPP_NODE, &cfg_no_esme_cmd);
+
+ install_node(&esme_node, config_write_esme);
+ vty_install_default(SMPP_ESME_NODE);
+ install_element(SMPP_ESME_NODE, &cfg_esme_passwd_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_passwd_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_route_pfx_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_route_pfx_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_defaultroute_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_defaultroute_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_del_src_imsi_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_del_src_imsi_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_osmo_ext_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_osmo_ext_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_dcs_transp_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_dcs_transp_cmd);
+
+ install_element_ve(&show_esme_cmd);
+
+ return 0;
+}
diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c
new file mode 100644
index 000000000..dc7f6e8c6
--- /dev/null
+++ b/src/libmsc/sms_queue.c
@@ -0,0 +1,544 @@
+/* SMS queue to continously attempt to deliver SMS */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * The difficulty of such a queue is to send a lot of SMS without
+ * overloading the paging subsystem and the database and other users
+ * of the MSC. To make the best use we would need to know the number
+ * of pending paging requests, then throttle the number of SMS we
+ * want to send and such.
+ * We will start with a very simple SMS Queue and then try to speed
+ * things up by collecting data from other parts of the system.
+ */
+
+#include <openbsc/sms_queue.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+/*
+ * One pending SMS that we wait for.
+ */
+struct gsm_sms_pending {
+ struct llist_head entry;
+
+ struct gsm_subscriber *subscr;
+ unsigned long long sms_id;
+ int failed_attempts;
+ int resend;
+};
+
+struct gsm_sms_queue {
+ struct osmo_timer_list resend_pending;
+ struct osmo_timer_list push_queue;
+ struct gsm_network *network;
+ int max_fail;
+ int max_pending;
+ int pending;
+
+ struct llist_head pending_sms;
+ unsigned long long last_subscr_id;
+};
+
+static int sms_subscr_cb(unsigned int, unsigned int, void *, void *);
+static int sms_sms_cb(unsigned int, unsigned int, void *, void *);
+
+static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq,
+ struct gsm_sms *sms)
+{
+ struct gsm_sms_pending *pending;
+
+ llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+ if (pending->sms_id == sms->id)
+ return pending;
+ }
+
+ return NULL;
+}
+
+static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms)
+{
+ return sms_find_pending(smsq, sms) != NULL;
+}
+
+static struct gsm_sms_pending *sms_subscriber_find_pending(
+ struct gsm_sms_queue *smsq,
+ struct gsm_subscriber *subscr)
+{
+ struct gsm_sms_pending *pending;
+
+ llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+ if (pending->subscr == subscr)
+ return pending;
+ }
+
+ return NULL;
+}
+
+static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq,
+ struct gsm_subscriber *subscr)
+{
+ return sms_subscriber_find_pending(smsq, subscr) != NULL;
+}
+
+static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq,
+ struct gsm_sms *sms)
+{
+ struct gsm_sms_pending *pending;
+
+ pending = talloc_zero(smsq, struct gsm_sms_pending);
+ if (!pending)
+ return NULL;
+
+ pending->subscr = subscr_get(sms->receiver);
+ pending->sms_id = sms->id;
+ return pending;
+}
+
+static void sms_pending_free(struct gsm_sms_pending *pending)
+{
+ subscr_put(pending->subscr);
+ llist_del(&pending->entry);
+ talloc_free(pending);
+}
+
+static void sms_pending_resend(struct gsm_sms_pending *pending)
+{
+ struct gsm_sms_queue *smsq;
+ LOGP(DLSMS, LOGL_DEBUG,
+ "Scheduling resend of SMS %llu.\n", pending->sms_id);
+
+ pending->resend = 1;
+
+ smsq = pending->subscr->group->net->sms_queue;
+ if (osmo_timer_pending(&smsq->resend_pending))
+ return;
+
+ osmo_timer_schedule(&smsq->resend_pending, 1, 0);
+}
+
+static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error)
+{
+ struct gsm_sms_queue *smsq;
+
+ LOGP(DLSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n",
+ pending->sms_id, pending->failed_attempts);
+
+ smsq = pending->subscr->group->net->sms_queue;
+ if (++pending->failed_attempts < smsq->max_fail)
+ return sms_pending_resend(pending);
+
+ sms_pending_free(pending);
+ smsq->pending -= 1;
+ sms_queue_trigger(smsq);
+}
+
+/*
+ * Resend all SMS that are scheduled for a resend. This is done to
+ * avoid an immediate failure.
+ */
+static void sms_resend_pending(void *_data)
+{
+ struct gsm_sms_pending *pending, *tmp;
+ struct gsm_sms_queue *smsq = _data;
+
+ llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+ struct gsm_sms *sms;
+ if (!pending->resend)
+ continue;
+
+ sms = db_sms_get(smsq->network, pending->sms_id);
+
+ /* the sms is gone? Move to the next */
+ if (!sms) {
+ sms_pending_free(pending);
+ smsq->pending -= 1;
+ sms_queue_trigger(smsq);
+ } else {
+ pending->resend = 0;
+ gsm411_send_sms_subscr(sms->receiver, sms);
+ }
+ }
+}
+
+static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq)
+{
+ struct gsm_sms *sms;
+
+ sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10);
+ if (sms) {
+ smsq->last_subscr_id = sms->receiver->id + 1;
+ return sms;
+ }
+
+ /* need to wrap around */
+ smsq->last_subscr_id = 0;
+ sms = db_sms_get_unsent_by_subscr(smsq->network,
+ smsq->last_subscr_id, 10);
+ if (sms)
+ smsq->last_subscr_id = sms->receiver->id + 1;
+ return sms;
+}
+
+/**
+ * I will submit up to max_pending - pending SMS to the
+ * subsystem.
+ */
+static void sms_submit_pending(void *_data)
+{
+ struct gsm_sms_queue *smsq = _data;
+ int attempts = smsq->max_pending - smsq->pending;
+ int initialized = 0;
+ unsigned long long first_sub = 0;
+ int attempted = 0, rounds = 0;
+
+ LOGP(DLSMS, LOGL_DEBUG, "Attempting to send %d SMS\n", attempts);
+
+ do {
+ struct gsm_sms_pending *pending;
+ struct gsm_sms *sms;
+
+
+ sms = take_next_sms(smsq);
+ if (!sms) {
+ LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (%d attempted)\n",
+ attempted);
+ break;
+ }
+
+ rounds += 1;
+ LOGP(DLSMS, LOGL_DEBUG, "Sending SMS round %d\n", rounds);
+
+ /*
+ * This code needs to detect a loop. It assumes that no SMS
+ * will vanish during the time this is executed. We will remember
+ * the id of the first GSM subscriber we see and then will
+ * compare this. The Database code should make sure that we will
+ * see all other subscribers first before seeing this one again.
+ *
+ * It is always scary to have an infinite loop like this.
+ */
+ if (!initialized) {
+ first_sub = sms->receiver->id;
+ initialized = 1;
+ } else if (first_sub == sms->receiver->id) {
+ LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (loop) (%d attempted)\n",
+ attempted);
+ sms_free(sms);
+ break;
+ }
+
+ /* no need to send a pending sms */
+ if (sms_is_in_pending(smsq, sms)) {
+ LOGP(DLSMS, LOGL_DEBUG,
+ "SMSqueue with pending sms: %llu. Skipping\n", sms->id);
+ sms_free(sms);
+ continue;
+ }
+
+ /* no need to send a SMS with the same receiver */
+ if (sms_subscriber_is_pending(smsq, sms->receiver)) {
+ LOGP(DLSMS, LOGL_DEBUG,
+ "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id);
+ sms_free(sms);
+ continue;
+ }
+
+ pending = sms_pending_from(smsq, sms);
+ if (!pending) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "Failed to create pending SMS entry.\n");
+ sms_free(sms);
+ continue;
+ }
+
+ attempted += 1;
+ smsq->pending += 1;
+ llist_add_tail(&pending->entry, &smsq->pending_sms);
+ gsm411_send_sms_subscr(sms->receiver, sms);
+ } while (attempted < attempts && rounds < 1000);
+
+ LOGP(DLSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds);
+}
+
+/**
+ * Send the next SMS or trigger the queue
+ */
+static void sms_send_next(struct gsm_subscriber *subscr)
+{
+ struct gsm_sms_queue *smsq = subscr->group->net->sms_queue;
+ struct gsm_sms_pending *pending;
+ struct gsm_sms *sms;
+
+ /* the subscriber should not be in the queue */
+ OSMO_ASSERT(!sms_subscriber_is_pending(smsq, subscr));
+
+ /* check for more messages for this subscriber */
+ sms = db_sms_get_unsent_for_subscr(subscr);
+ if (!sms)
+ goto no_pending_sms;
+
+ /* No sms should be scheduled right now */
+ OSMO_ASSERT(!sms_is_in_pending(smsq, sms));
+
+ /* Remember that we deliver this SMS and send it */
+ pending = sms_pending_from(smsq, sms);
+ if (!pending) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "Failed to create pending SMS entry.\n");
+ sms_free(sms);
+ goto no_pending_sms;
+ }
+
+ smsq->pending += 1;
+ llist_add_tail(&pending->entry, &smsq->pending_sms);
+ gsm411_send_sms_subscr(sms->receiver, sms);
+ return;
+
+no_pending_sms:
+ /* Try to send the SMS to avoid the queue being stuck */
+ sms_submit_pending(subscr->group->net->sms_queue);
+}
+
+/*
+ * Kick off the queue again.
+ */
+int sms_queue_trigger(struct gsm_sms_queue *smsq)
+{
+ LOGP(DLSMS, LOGL_DEBUG, "Triggering SMS queue\n");
+ if (osmo_timer_pending(&smsq->push_queue))
+ return 0;
+
+ osmo_timer_schedule(&smsq->push_queue, 1, 0);
+ return 0;
+}
+
+int sms_queue_start(struct gsm_network *network, int max_pending)
+{
+ struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue);
+ if (!sms) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n");
+ return -1;
+ }
+
+ osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network);
+ osmo_signal_register_handler(SS_SMS, sms_sms_cb, network);
+
+ network->sms_queue = sms;
+ INIT_LLIST_HEAD(&sms->pending_sms);
+ sms->max_fail = 1;
+ sms->network = network;
+ sms->max_pending = max_pending;
+ osmo_timer_setup(&sms->push_queue, sms_submit_pending, sms);
+ osmo_timer_setup(&sms->resend_pending, sms_resend_pending, sms);
+
+ sms_submit_pending(sms);
+
+ return 0;
+}
+
+static int sub_ready_for_sm(struct gsm_network *net, struct gsm_subscriber *subscr)
+{
+ struct gsm_sms *sms;
+ struct gsm_sms_pending *pending;
+ struct gsm_subscriber_connection *conn;
+
+ /*
+ * The code used to be very clever and tried to submit
+ * a SMS during the Location Updating Request. This has
+ * two issues:
+ * 1.) The Phone might not be ready yet, e.g. the C155
+ * will not respond to the Submit when it is booting.
+ * 2.) The queue is already trying to submit SMS to the
+ * user and by not responding to the paging request
+ * we will set the LAC back to 0. We would have to
+ * stop the paging and move things over.
+ *
+ * We need to be careful in what we try here.
+ */
+
+ /* check if we have pending requests */
+ pending = sms_subscriber_find_pending(net->sms_queue, subscr);
+ if (pending) {
+ LOGP(DMSC, LOGL_NOTICE,
+ "Pending paging while subscriber %llu attached.\n",
+ subscr->id);
+ return 0;
+ }
+
+ conn = connection_for_subscr(subscr);
+ if (!conn)
+ return -1;
+
+ /* Now try to deliver any pending SMS to this sub */
+ sms = db_sms_get_unsent_for_subscr(subscr);
+ if (!sms)
+ return -1;
+ gsm411_send_sms(conn, sms);
+ return 0;
+}
+
+static int sms_subscr_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_subscriber *subscr = signal_data;
+
+ if (signal != S_SUBSCR_ATTACHED)
+ return 0;
+
+ /* this is readyForSM */
+ return sub_ready_for_sm(handler_data, subscr);
+}
+
+static int sms_sms_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_network *network = handler_data;
+ struct sms_signal_data *sig_sms = signal_data;
+ struct gsm_sms_pending *pending;
+ struct gsm_subscriber *subscr;
+
+ /* We got a new SMS and maybe should launch the queue again. */
+ if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) {
+ /* TODO: For SMMA we might want to re-use the radio connection. */
+ sms_queue_trigger(network->sms_queue);
+ return 0;
+ }
+
+ if (!sig_sms->sms)
+ return -1;
+
+
+ /*
+ * Find the entry of our queue. The SMS subsystem will submit
+ * sms that are not in our control as we just have a channel
+ * open anyway.
+ */
+ pending = sms_find_pending(network->sms_queue, sig_sms->sms);
+ if (!pending)
+ return 0;
+
+ switch (signal) {
+ case S_SMS_DELIVERED:
+ /* Remember the subscriber and clear the pending entry */
+ network->sms_queue->pending -= 1;
+ subscr = subscr_get(pending->subscr);
+ sms_pending_free(pending);
+ /* Attempt to send another SMS to this subscriber */
+ sms_send_next(subscr);
+ subscr_put(subscr);
+ break;
+ case S_SMS_MEM_EXCEEDED:
+ network->sms_queue->pending -= 1;
+ sms_pending_free(pending);
+ sms_queue_trigger(network->sms_queue);
+ break;
+ case S_SMS_UNKNOWN_ERROR:
+ /*
+ * There can be many reasons for this failure. E.g. the paging
+ * timed out, the subscriber was not paged at all, or there was
+ * a protocol error. The current strategy is to try sending the
+ * next SMS for busy/oom and to retransmit when we have paged.
+ *
+ * When the paging expires three times we will disable the
+ * subscriber. If we have some kind of other transmit error we
+ * should flag the SMS as bad.
+ */
+ switch (sig_sms->paging_result) {
+ case 0:
+ /* BAD SMS? */
+ db_sms_inc_deliver_attempts(sig_sms->sms);
+ sms_pending_failed(pending, 0);
+ break;
+ case GSM_PAGING_EXPIRED:
+ sms_pending_failed(pending, 1);
+ break;
+
+ case GSM_PAGING_OOM:
+ case GSM_PAGING_BUSY:
+ network->sms_queue->pending -= 1;
+ sms_pending_free(pending);
+ sms_queue_trigger(network->sms_queue);
+ break;
+ default:
+ LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n",
+ sig_sms->paging_result);
+ }
+ break;
+ default:
+ LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n",
+ sig_sms->paging_result);
+ }
+
+ return 0;
+}
+
+/* VTY helper functions */
+int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty)
+{
+ struct gsm_sms_pending *pending;
+
+ vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s",
+ smsq->max_pending, smsq->pending, VTY_NEWLINE);
+
+ llist_for_each_entry(pending, &smsq->pending_sms, entry)
+ vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s",
+ pending->subscr->id, pending->sms_id,
+ pending->failed_attempts, VTY_NEWLINE);
+ return 0;
+}
+
+int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending)
+{
+ LOGP(DLSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n",
+ smsq->max_pending, max_pending);
+ smsq->max_pending = max_pending;
+ return 0;
+}
+
+int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail)
+{
+ LOGP(DLSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n",
+ smsq->max_fail, max_fail);
+ smsq->max_fail = max_fail;
+ return 0;
+}
+
+int sms_queue_clear(struct gsm_sms_queue *smsq)
+{
+ struct gsm_sms_pending *pending, *tmp;
+
+ llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+ LOGP(DLSMS, LOGL_NOTICE,
+ "SMSqueue clearing for sub %llu\n", pending->subscr->id);
+ sms_pending_free(pending);
+ }
+
+ smsq->pending = 0;
+ return 0;
+}
diff --git a/src/libmsc/token_auth.c b/src/libmsc/token_auth.c
new file mode 100644
index 000000000..5af1e980b
--- /dev/null
+++ b/src/libmsc/token_auth.c
@@ -0,0 +1,160 @@
+/* SMS based token authentication for ad-hoc GSM networks */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+
+#define TOKEN_SMS_TEXT "HAR 2009 GSM. Register at http://har2009.gnumonks.org/ " \
+ "Your IMSI is %s, auth token is %08X, phone no is %s."
+
+static char *build_sms_string(struct gsm_subscriber *subscr, uint32_t token)
+{
+ char *sms_str;
+ unsigned int len;
+
+ len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT);
+ sms_str = talloc_size(tall_bsc_ctx, len);
+ if (!sms_str)
+ return NULL;
+
+ snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token,
+ subscr->extension);
+ sms_str[len-1] = '\0';
+
+ return sms_str;
+}
+
+static int token_subscr_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_subscriber *subscr = signal_data;
+ struct gsm_sms *sms;
+ int rc = 0;
+
+ if (signal != S_SUBSCR_ATTACHED)
+ return 0;
+
+ if (subscr->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+ return 0;
+
+ if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) {
+ struct gsm_subscriber *sender;
+ uint32_t token;
+ char *sms_str;
+
+ /* we've seen this subscriber for the first time. */
+ rc = db_subscriber_alloc_token(subscr, &token);
+ if (rc != 0) {
+ rc = -EIO;
+ goto unauth;
+ }
+
+ sms_str = build_sms_string(subscr, token);
+ if (!sms_str) {
+ rc = -ENOMEM;
+ goto unauth;
+ }
+
+
+ /* FIXME: don't use ID 1 static */
+ sender = subscr_get_by_id(subscr->group, 1);
+
+ sms = sms_from_text(subscr, sender, 0, sms_str);
+
+ subscr_put(sender);
+ talloc_free(sms_str);
+ if (!sms) {
+ rc = -ENOMEM;
+ goto unauth;
+ }
+
+ rc = gsm411_send_sms_subscr(subscr, sms);
+
+ /* FIXME: else, delete the subscirber from database */
+unauth:
+
+ /* make sure we don't allow him in again unless he clicks the web UI */
+ subscr->authorized = 0;
+ db_sync_subscriber(subscr);
+ if (rc) {
+ struct gsm_subscriber_connection *conn = connection_for_subscr(subscr);
+ if (conn) {
+ uint8_t auth_rand[16];
+ /* kick the subscriber off the network */
+ gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 0);
+ gsm48_tx_mm_auth_rej(conn);
+ /* FIXME: close the channel early ?*/
+ //gsm48_send_rr_Release(lchan);
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int token_sms_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct sms_signal_data *sig = signal_data;
+ struct gsm_sms *sms = sig->sms;;
+ struct gsm_subscriber_connection *conn;
+ uint8_t auth_rand[16];
+
+
+ if (signal != S_SMS_DELIVERED)
+ return 0;
+
+
+ /* these are not the droids we've been looking for */
+ if (!sms->receiver ||
+ !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT))
+ return 0;
+
+
+ if (sms->receiver->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+ return 0;
+
+
+ conn = connection_for_subscr(sms->receiver);
+ if (conn) {
+ /* kick the subscriber off the network */
+ gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 0);
+ gsm48_tx_mm_auth_rej(conn);
+ /* FIXME: close the channel early ?*/
+ //gsm48_send_rr_Release(lchan);
+ }
+
+ return 0;
+}
+
+//static __attribute__((constructor)) void on_dso_load_token(void)
+void on_dso_load_token(void)
+{
+ osmo_signal_register_handler(SS_SUBSCR, token_subscr_cb, NULL);
+ osmo_signal_register_handler(SS_SMS, token_sms_cb, NULL);
+}
diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c
new file mode 100644
index 000000000..dba4bed17
--- /dev/null
+++ b/src/libmsc/transaction.c
@@ -0,0 +1,163 @@
+/* GSM 04.07 Transaction handling */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/transaction.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mncc.h>
+#include <openbsc/debug.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mncc.h>
+#include <openbsc/paging.h>
+#include <openbsc/osmo_msc.h>
+
+void *tall_trans_ctx;
+
+void _gsm48_cc_trans_free(struct gsm_trans *trans);
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn,
+ uint8_t proto, uint8_t trans_id)
+{
+ struct gsm_trans *trans;
+ struct gsm_network *net = conn->network;
+ struct gsm_subscriber *subscr = conn->subscr;
+
+ llist_for_each_entry(trans, &net->trans_list, entry) {
+ if (trans->subscr == subscr &&
+ trans->protocol == proto &&
+ trans->transaction_id == trans_id)
+ return trans;
+ }
+ return NULL;
+}
+
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+ uint32_t callref)
+{
+ struct gsm_trans *trans;
+
+ llist_for_each_entry(trans, &net->trans_list, entry) {
+ if (trans->callref == callref)
+ return trans;
+ }
+ return NULL;
+}
+
+struct gsm_trans *trans_alloc(struct gsm_network *net,
+ struct gsm_subscriber *subscr,
+ uint8_t protocol, uint8_t trans_id,
+ uint32_t callref)
+{
+ struct gsm_trans *trans;
+
+ DEBUGP(DCC, "subscr=%p, net=%p\n", subscr, net);
+
+ trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
+ if (!trans)
+ return NULL;
+
+ trans->subscr = subscr;
+ subscr_get(trans->subscr);
+
+ trans->protocol = protocol;
+ trans->transaction_id = trans_id;
+ trans->callref = callref;
+
+ trans->net = net;
+ llist_add_tail(&trans->entry, &net->trans_list);
+
+ return trans;
+}
+
+void trans_free(struct gsm_trans *trans)
+{
+ switch (trans->protocol) {
+ case GSM48_PDISC_CC:
+ _gsm48_cc_trans_free(trans);
+ break;
+ case GSM48_PDISC_SMS:
+ _gsm411_sms_trans_free(trans);
+ break;
+ }
+
+ if (trans->paging_request) {
+ subscr_remove_request(trans->paging_request);
+ trans->paging_request = NULL;
+ }
+
+ if (trans->subscr) {
+ subscr_put(trans->subscr);
+ trans->subscr = NULL;
+ }
+
+ llist_del(&trans->entry);
+
+ if (trans->conn)
+ msc_release_connection(trans->conn);
+
+ trans->conn = NULL;
+ talloc_free(trans);
+}
+
+/* allocate an unused transaction ID for the given subscriber
+ * in the given protocol using the ti_flag specified */
+int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr,
+ uint8_t protocol, uint8_t ti_flag)
+{
+ struct gsm_trans *trans;
+ unsigned int used_tid_bitmask = 0;
+ int i, j, h;
+
+ if (ti_flag)
+ ti_flag = 0x8;
+
+ /* generate bitmask of already-used TIDs for this (subscr,proto) */
+ llist_for_each_entry(trans, &net->trans_list, entry) {
+ if (trans->subscr != subscr ||
+ trans->protocol != protocol ||
+ trans->transaction_id == 0xff)
+ continue;
+ used_tid_bitmask |= (1 << trans->transaction_id);
+ }
+
+ /* find a new one, trying to go in a 'circular' pattern */
+ for (h = 6; h > 0; h--)
+ if (used_tid_bitmask & (1 << (h | ti_flag)))
+ break;
+ for (i = 0; i < 7; i++) {
+ j = ((h + i) % 7) | ti_flag;
+ if ((used_tid_bitmask & (1 << j)) == 0)
+ return j;
+ }
+
+ return -1;
+}
+
+int trans_has_conn(const struct gsm_subscriber_connection *conn)
+{
+ struct gsm_trans *trans;
+
+ llist_for_each_entry(trans, &conn->network->trans_list, entry)
+ if (trans->conn == conn)
+ return 1;
+
+ return 0;
+}
diff --git a/src/libmsc/ussd.c b/src/libmsc/ussd.c
new file mode 100644
index 000000000..f12c1f281
--- /dev/null
+++ b/src/libmsc/ussd.c
@@ -0,0 +1,95 @@
+/* Network-specific handling of mobile-originated USSDs. */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* This module defines the network-specific handling of mobile-originated
+ USSD messages. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/osmo_msc.h>
+
+/* Declarations of USSD strings to be recognised */
+const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
+
+/* Forward declarations of network-specific handler functions */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ss_request *req);
+
+
+/* Entrypoint - handler function common to all mobile-originated USSDs */
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ int rc;
+ struct ss_request req;
+ struct gsm48_hdr *gh;
+
+ memset(&req, 0, sizeof(req));
+ gh = msgb_l3(msg);
+ rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req);
+ if (!rc) {
+ DEBUGP(DMM, "Unhandled SS\n");
+ rc = gsm0480_send_ussd_reject(conn, msg, &req);
+ msc_release_connection(conn);
+ return rc;
+ }
+
+ /* Interrogation or releaseComplete? */
+ if (req.ussd_text[0] == '\0' || req.ussd_text[0] == 0xFF) {
+ if (req.ss_code > 0) {
+ /* Assume interrogateSS or modification of it and reject */
+ rc = gsm0480_send_ussd_reject(conn, msg, &req);
+ msc_release_connection(conn);
+ return rc;
+ }
+ /* Still assuming a Release-Complete and returning */
+ return 0;
+ }
+
+ if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)req.ussd_text)) {
+ DEBUGP(DMM, "USSD: Own number requested\n");
+ rc = send_own_number(conn, msg, &req);
+ } else {
+ DEBUGP(DMM, "Unhandled USSD %s\n", req.ussd_text);
+ rc = gsm0480_send_ussd_reject(conn, msg, &req);
+ }
+
+ /* check if we can release it */
+ msc_release_connection(conn);
+ return rc;
+}
+
+/* A network-specific handler function */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ss_request *req)
+{
+ char *own_number = conn->subscr->extension;
+ char response_string[GSM_EXTENSION_LENGTH + 20];
+
+ /* Need trailing CR as EOT character */
+ snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number);
+ return gsm0480_send_ussd_response(conn, msg, response_string, req);
+}
diff --git a/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c
new file mode 100644
index 000000000..e50329104
--- /dev/null
+++ b/src/libmsc/vty_interface_layer3.c
@@ -0,0 +1,1210 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <time.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/gsm_04_11.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/db.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/sms_queue.h>
+#include <openbsc/mncc_int.h>
+#include <openbsc/handover.h>
+
+#include <osmocom/vty/logging.h>
+
+#include "meas_feed.h"
+
+extern struct gsm_network *gsmnet_from_vty(struct vty *v);
+
+static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+ int rc;
+ int reqs;
+ struct gsm_auth_info ainfo;
+ struct gsm_auth_tuple atuple;
+ struct llist_head *entry;
+ char expire_time[200];
+
+ vty_out(vty, " ID: %llu, Authorized: %d%s", subscr->id,
+ subscr->authorized, VTY_NEWLINE);
+ if (strlen(subscr->name))
+ vty_out(vty, " Name: '%s'%s", subscr->name, VTY_NEWLINE);
+ if (strlen(subscr->extension))
+ vty_out(vty, " Extension: %s%s", subscr->extension,
+ VTY_NEWLINE);
+ vty_out(vty, " LAC: %d/0x%x%s",
+ subscr->lac, subscr->lac, VTY_NEWLINE);
+ vty_out(vty, " IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+ if (subscr->tmsi != GSM_RESERVED_TMSI)
+ vty_out(vty, " TMSI: %08X%s", subscr->tmsi,
+ VTY_NEWLINE);
+
+ rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+ if (!rc) {
+ vty_out(vty, " A3A8 algorithm id: %d%s",
+ ainfo.auth_algo, VTY_NEWLINE);
+ vty_out(vty, " A3A8 Ki: %s%s",
+ osmo_hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len),
+ VTY_NEWLINE);
+ }
+
+ rc = db_get_lastauthtuple_for_subscr(&atuple, subscr);
+ if (!rc) {
+ vty_out(vty, " A3A8 last tuple (used %d times):%s",
+ atuple.use_count, VTY_NEWLINE);
+ vty_out(vty, " seq # : %d%s",
+ atuple.key_seq, VTY_NEWLINE);
+ vty_out(vty, " RAND : %s%s",
+ osmo_hexdump(atuple.vec.rand, sizeof(atuple.vec.rand)),
+ VTY_NEWLINE);
+ vty_out(vty, " SRES : %s%s",
+ osmo_hexdump(atuple.vec.sres, sizeof(atuple.vec.sres)),
+ VTY_NEWLINE);
+ vty_out(vty, " Kc : %s%s",
+ osmo_hexdump(atuple.vec.kc, sizeof(atuple.vec.kc)),
+ VTY_NEWLINE);
+ }
+
+ /* print the expiration time of a subscriber */
+ strftime(expire_time, sizeof(expire_time),
+ "%a, %d %b %Y %T %z", localtime(&subscr->expire_lu));
+ expire_time[sizeof(expire_time) - 1] = '\0';
+ vty_out(vty, " Expiration Time: %s%s", expire_time, VTY_NEWLINE);
+
+ reqs = 0;
+ llist_for_each(entry, &subscr->requests)
+ reqs += 1;
+ vty_out(vty, " Paging: %s paging Requests: %d%s",
+ subscr->is_paging ? "is" : "not", reqs, VTY_NEWLINE);
+ vty_out(vty, " Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+
+/* Subscriber */
+DEFUN(show_subscr_cache,
+ show_subscr_cache_cmd,
+ "show subscriber cache",
+ SHOW_STR "Show information about subscribers\n"
+ "Display contents of subscriber cache\n")
+{
+ struct gsm_subscriber *subscr;
+
+ llist_for_each_entry(subscr, &active_subscribers, entry) {
+ vty_out(vty, " Subscriber:%s", VTY_NEWLINE);
+ subscr_dump_full_vty(vty, subscr);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(sms_send_pend,
+ sms_send_pend_cmd,
+ "sms send pending",
+ "SMS related commands\n" "SMS Sending related commands\n"
+ "Send all pending SMS")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_sms *sms;
+ int id = 0;
+
+ while (1) {
+ sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX);
+ if (!sms)
+ break;
+
+ gsm411_send_sms_subscr(sms->receiver, sms);
+
+ id = sms->receiver->id + 1;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int _send_sms_str(struct gsm_subscriber *receiver,
+ struct gsm_subscriber *sender,
+ char *str, uint8_t tp_pid)
+{
+ struct gsm_sms *sms;
+
+ sms = sms_from_text(receiver, sender, 0, str);
+ sms->protocol_id = tp_pid;
+
+ /* store in database for the queue */
+ if (db_sms_store(sms) != 0) {
+ LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+ sms_free(sms);
+ return CMD_WARNING;
+ }
+ LOGP(DLSMS, LOGL_DEBUG, "SMS stored in DB\n");
+
+ sms_free(sms);
+ sms_queue_trigger(receiver->group->net->sms_queue);
+ return CMD_SUCCESS;
+}
+
+static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet,
+ const char *type,
+ const char *id)
+{
+ if (!strcmp(type, "extension"))
+ return subscr_get_by_extension(gsmnet->subscr_group, id);
+ else if (!strcmp(type, "imsi"))
+ return subscr_get_by_imsi(gsmnet->subscr_group, id);
+ else if (!strcmp(type, "tmsi"))
+ return subscr_get_by_tmsi(gsmnet->subscr_group, atoi(id));
+ else if (!strcmp(type, "id"))
+ return subscr_get_by_id(gsmnet->subscr_group, atoi(id));
+
+ return NULL;
+}
+#define SUBSCR_TYPES "(extension|imsi|tmsi|id)"
+#define SUBSCR_HELP "Operations on a Subscriber\n" \
+ "Identify subscriber by extension (phone number)\n" \
+ "Identify subscriber by IMSI\n" \
+ "Identify subscriber by TMSI\n" \
+ "Identify subscriber by database ID\n" \
+ "Identifier for the subscriber\n"
+
+DEFUN(show_subscr,
+ show_subscr_cmd,
+ "show subscriber " SUBSCR_TYPES " ID",
+ SHOW_STR SUBSCR_HELP)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ subscr_dump_full_vty(vty, subscr);
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_create,
+ subscriber_create_cmd,
+ "subscriber create imsi ID",
+ "Operations on a Subscriber\n" \
+ "Create new subscriber\n" \
+ "Identify the subscriber by his IMSI\n" \
+ "Identifier for the subscriber\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr;
+
+ subscr = subscr_get_by_imsi(gsmnet->subscr_group, argv[0]);
+ if (subscr)
+ db_sync_subscriber(subscr);
+ else {
+ subscr = subscr_create_subscriber(gsmnet->subscr_group, argv[0]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber created for IMSI %s%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ /* Show info about the created subscriber. */
+ subscr_dump_full_vty(vty, subscr);
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_pending_sms,
+ subscriber_send_pending_sms_cmd,
+ "subscriber " SUBSCR_TYPES " ID sms pending-send",
+ SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr;
+ struct gsm_sms *sms;
+
+ subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX);
+ if (sms)
+ gsm411_send_sms_subscr(sms->receiver, sms);
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_sms,
+ subscriber_send_sms_cmd,
+ "subscriber " SUBSCR_TYPES " ID sms sender " SUBSCR_TYPES " SENDER_ID send .LINE",
+ SUBSCR_HELP "SMS Operations\n" SUBSCR_HELP "Send SMS\n" "Actual SMS Text\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]);
+ char *str;
+ int rc;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ rc = CMD_WARNING;
+ goto err;
+ }
+
+ if (!sender) {
+ vty_out(vty, "%% No sender found for %s %s%s",
+ argv[2], argv[3], VTY_NEWLINE);
+ rc = CMD_WARNING;
+ goto err;
+ }
+
+ str = argv_concat(argv, argc, 4);
+ rc = _send_sms_str(subscr, sender, str, 0);
+ talloc_free(str);
+
+err:
+ if (sender)
+ subscr_put(sender);
+
+ if (subscr)
+ subscr_put(subscr);
+
+ return rc;
+}
+
+DEFUN(subscriber_silent_sms,
+ subscriber_silent_sms_cmd,
+
+ "subscriber " SUBSCR_TYPES " ID silent-sms sender " SUBSCR_TYPES " SENDER_ID send .LINE",
+ SUBSCR_HELP "Silent SMS Operations\n" SUBSCR_HELP "Send SMS\n" "Actual SMS Text\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]);
+ char *str;
+ int rc;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ rc = CMD_WARNING;
+ goto err;
+ }
+
+ if (!sender) {
+ vty_out(vty, "%% No sender found for %s %s%s",
+ argv[2], argv[3], VTY_NEWLINE);
+ rc = CMD_WARNING;
+ goto err;
+ }
+
+ str = argv_concat(argv, argc, 4);
+ rc = _send_sms_str(subscr, sender, str, 64);
+ talloc_free(str);
+
+err:
+ if (sender)
+ subscr_put(sender);
+
+ if (subscr)
+ subscr_put(subscr);
+
+ return rc;
+}
+
+#define CHAN_TYPES "(any|tch/f|tch/any|sdcch)"
+#define CHAN_TYPE_HELP \
+ "Any channel\n" \
+ "TCH/F channel\n" \
+ "Any TCH channel\n" \
+ "SDCCH channel\n"
+
+DEFUN(subscriber_silent_call_start,
+ subscriber_silent_call_start_cmd,
+ "subscriber " SUBSCR_TYPES " ID silent-call start (any|tch/f|tch/any|sdcch)",
+ SUBSCR_HELP "Silent call operation\n" "Start silent call\n"
+ CHAN_TYPE_HELP)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ int rc, type;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[2], "tch/f"))
+ type = RSL_CHANNEED_TCH_F;
+ else if (!strcmp(argv[2], "tch/any"))
+ type = RSL_CHANNEED_TCH_ForH;
+ else if (!strcmp(argv[2], "sdcch"))
+ type = RSL_CHANNEED_SDCCH;
+ else
+ type = RSL_CHANNEED_ANY; /* Defaults to ANY */
+
+ rc = gsm_silent_call_start(subscr, vty, type);
+ if (rc <= 0) {
+ vty_out(vty, "%% Subscriber not attached%s",
+ VTY_NEWLINE);
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_silent_call_stop,
+ subscriber_silent_call_stop_cmd,
+ "subscriber " SUBSCR_TYPES " ID silent-call stop",
+ SUBSCR_HELP "Silent call operation\n" "Stop silent call\n"
+ CHAN_TYPE_HELP)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ int rc;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gsm_silent_call_stop(subscr);
+ if (rc < 0) {
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_ussd_notify,
+ subscriber_ussd_notify_cmd,
+ "subscriber " SUBSCR_TYPES " ID ussd-notify (0|1|2) .TEXT",
+ SUBSCR_HELP "Send a USSD notify to the subscriber\n"
+ "Alerting Level 0\n"
+ "Alerting Level 1\n"
+ "Alerting Level 2\n"
+ "Text of USSD message to send\n")
+{
+ char *text;
+ struct gsm_subscriber_connection *conn;
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ int level;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ level = atoi(argv[2]);
+ text = argv_concat(argv, argc, 3);
+ if (!text) {
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ conn = connection_for_subscr(subscr);
+ if (!conn) {
+ vty_out(vty, "%% An active connection is required for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ subscr_put(subscr);
+ talloc_free(text);
+ return CMD_WARNING;
+ }
+
+ msc_send_ussd_notify(conn, level, text);
+ msc_send_ussd_release_complete(conn);
+
+ subscr_put(subscr);
+ talloc_free(text);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_delete,
+ ena_subscr_delete_cmd,
+ "subscriber " SUBSCR_TYPES " ID delete",
+ SUBSCR_HELP "Delete subscriber in HLR\n")
+{
+ int rc;
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (subscr->use_count != 1) {
+ vty_out(vty, "Removing active subscriber%s", VTY_NEWLINE);
+ }
+
+ rc = db_subscriber_delete(subscr);
+ subscr_put(subscr);
+
+ if (rc != 0) {
+ vty_out(vty, "Failed to remove subscriber%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_expire,
+ ena_subscr_expire_cmd,
+ "subscriber " SUBSCR_TYPES " ID expire",
+ SUBSCR_HELP "Expire the subscriber Now\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ subscr->expire_lu = time(0);
+ db_sync_subscriber(subscr);
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_authorized,
+ ena_subscr_authorized_cmd,
+ "subscriber " SUBSCR_TYPES " ID authorized (0|1)",
+ SUBSCR_HELP "(De-)Authorize subscriber in HLR\n"
+ "Subscriber should NOT be authorized\n"
+ "Subscriber should be authorized\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ subscr->authorized = atoi(argv[2]);
+ db_sync_subscriber(subscr);
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_name,
+ ena_subscr_name_cmd,
+ "subscriber " SUBSCR_TYPES " ID name .NAME",
+ SUBSCR_HELP "Set the name of the subscriber\n"
+ "Name of the Subscriber\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ char *name;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ name = argv_concat(argv, argc, 2);
+ if (!name) {
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ if (strlen(name) > sizeof(subscr->name)-1) {
+ vty_out(vty,
+ "%% NAME is too long, max. %zu characters are allowed%s",
+ sizeof(subscr->name)-1, VTY_NEWLINE);
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ osmo_strlcpy(subscr->name, name, sizeof(subscr->name));
+ talloc_free(name);
+ db_sync_subscriber(subscr);
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_extension,
+ ena_subscr_extension_cmd,
+ "subscriber " SUBSCR_TYPES " ID extension EXTENSION",
+ SUBSCR_HELP "Set the extension (phone number) of the subscriber\n"
+ "Extension (phone number)\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ const char *ext = argv[2];
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strlen(ext) > sizeof(subscr->extension)-1) {
+ vty_out(vty,
+ "%% EXTENSION is too long, max. %zu characters are allowed%s",
+ sizeof(subscr->extension)-1, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_strlcpy(subscr->extension, ext, sizeof(subscr->extension));
+ db_sync_subscriber(subscr);
+
+ subscr_put(subscr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_handover,
+ ena_subscr_handover_cmd,
+ "subscriber " SUBSCR_TYPES " ID handover BTS_NR",
+ SUBSCR_HELP "Handover the active connection\n"
+ "Number of the BTS to handover to\n")
+{
+ int ret;
+ struct gsm_subscriber_connection *conn;
+ struct gsm_bts *bts;
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s.%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ conn = connection_for_subscr(subscr);
+ if (!conn) {
+ vty_out(vty, "%% No active connection for subscriber %s %s.%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ bts = gsm_bts_num(gsmnet, atoi(argv[2]));
+ if (!bts) {
+ vty_out(vty, "%% BTS with number(%d) could not be found.%s",
+ atoi(argv[2]), VTY_NEWLINE);
+ subscr_put(subscr);
+ return CMD_WARNING;
+ }
+
+ /* now start the handover */
+ ret = bsc_handover_start(conn->lchan, bts);
+ if (ret != 0) {
+ vty_out(vty, "%% Handover failed with errno %d.%s",
+ ret, VTY_NEWLINE);
+ } else {
+ vty_out(vty, "%% Handover started from %s",
+ gsm_lchan_name(conn->lchan));
+ vty_out(vty, " to %s.%s", gsm_lchan_name(conn->ho_lchan),
+ VTY_NEWLINE);
+ }
+
+ subscr_put(subscr);
+ return CMD_SUCCESS;
+}
+
+#define A3A8_ALG_TYPES "(none|xor|comp128v1)"
+#define A3A8_ALG_HELP \
+ "Use No A3A8 algorithm\n" \
+ "Use XOR algorithm\n" \
+ "Use COMP128v1 algorithm\n"
+
+DEFUN(ena_subscr_a3a8,
+ ena_subscr_a3a8_cmd,
+ "subscriber " SUBSCR_TYPES " ID a3a8 " A3A8_ALG_TYPES " [KI]",
+ SUBSCR_HELP "Set a3a8 parameters for the subscriber\n"
+ A3A8_ALG_HELP "Encryption Key Ki\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr =
+ get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+ const char *alg_str = argv[2];
+ const char *ki_str = argc == 4 ? argv[3] : NULL;
+ struct gsm_auth_info ainfo;
+ int rc, minlen, maxlen;
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcasecmp(alg_str, "none")) {
+ ainfo.auth_algo = AUTH_ALGO_NONE;
+ minlen = maxlen = 0;
+ } else if (!strcasecmp(alg_str, "xor")) {
+ ainfo.auth_algo = AUTH_ALGO_XOR;
+ minlen = A38_XOR_MIN_KEY_LEN;
+ maxlen = A38_XOR_MAX_KEY_LEN;
+ } else if (!strcasecmp(alg_str, "comp128v1")) {
+ ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+ minlen = maxlen = A38_COMP128_KEY_LEN;
+ } else {
+ /* Unknown method */
+ subscr_put(subscr);
+ vty_out(vty, "%% Unknown auth method %s%s",
+ alg_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (ki_str) {
+ rc = osmo_hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+ if ((rc > maxlen) || (rc < minlen)) {
+ subscr_put(subscr);
+ vty_out(vty, "%% Wrong Ki `%s'%s",
+ ki_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ainfo.a3a8_ki_len = rc;
+ } else {
+ ainfo.a3a8_ki_len = 0;
+ if (minlen) {
+ subscr_put(subscr);
+ vty_out(vty, "%% Missing Ki argument%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ rc = db_sync_authinfo_for_subscr(
+ ainfo.auth_algo == AUTH_ALGO_NONE ? NULL : &ainfo,
+ subscr);
+
+ /* the last tuple probably invalid with the new auth settings */
+ db_sync_lastauthtuple_for_subscr(NULL, subscr);
+ subscr_put(subscr);
+
+ if (rc) {
+ vty_out(vty, "%% Operation has failed%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_purge,
+ subscriber_purge_cmd,
+ "subscriber purge-inactive",
+ "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int purged;
+
+ purged = subscr_purge_inactive(net->subscr_group);
+ vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_update,
+ subscriber_update_cmd,
+ "subscriber " SUBSCR_TYPES " ID update",
+ SUBSCR_HELP "Update the subscriber data from the dabase.\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+ if (!subscr) {
+ vty_out(vty, "%% No subscriber found for %s %s%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ subscr_update_from_db(subscr);
+ subscr_put(subscr);
+ return CMD_SUCCESS;
+}
+
+static int scall_cbfn(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct scall_signal_data *sigdata = signal_data;
+ struct vty *vty = sigdata->data;
+
+ switch (signal) {
+ case S_SCALL_SUCCESS:
+ vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s",
+ sigdata->conn->lchan->ts->trx->arfcn, sigdata->conn->lchan->ts->nr,
+ VTY_NEWLINE);
+ break;
+ case S_SCALL_EXPIRED:
+ vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE);
+ break;
+ }
+ return 0;
+}
+
+DEFUN(show_stats,
+ show_stats_cmd,
+ "show statistics",
+ SHOW_STR "Display network statistics\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ openbsc_vty_print_statistics(vty, net);
+ vty_out(vty, "Location Update : %lu attach, %lu normal, %lu periodic%s",
+ net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH].current,
+ net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL].current,
+ net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC].current,
+ VTY_NEWLINE);
+ vty_out(vty, "IMSI Detach Indications : %lu%s",
+ net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH].current,
+ VTY_NEWLINE);
+ vty_out(vty, "Location Updating Results: %lu completed, %lu failed%s",
+ net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED].current,
+ net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED].current,
+ VTY_NEWLINE);
+ vty_out(vty, "Handover : %lu attempted, %lu no_channel, %lu timeout, "
+ "%lu completed, %lu failed%s",
+ net->msc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED].current,
+ net->msc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL].current,
+ net->msc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT].current,
+ net->msc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED].current,
+ net->msc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED].current,
+ VTY_NEWLINE);
+ vty_out(vty, "SMS MO : %lu submitted, %lu no receiver%s",
+ net->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED].current,
+ net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER].current,
+ VTY_NEWLINE);
+ vty_out(vty, "SMS MT : %lu delivered, %lu no memory, %lu other error%s",
+ net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED].current,
+ net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM].current,
+ net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER].current,
+ VTY_NEWLINE);
+ vty_out(vty, "MO Calls : %lu setup, %lu connect ack%s",
+ net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP].current,
+ net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK].current,
+ VTY_NEWLINE);
+ vty_out(vty, "MT Calls : %lu setup, %lu connect%s",
+ net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP].current,
+ net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT].current,
+ VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_smsqueue,
+ show_smsqueue_cmd,
+ "show sms-queue",
+ SHOW_STR "Display SMSqueue statistics\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ sms_queue_stats(net->sms_queue, vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_trigger,
+ smsqueue_trigger_cmd,
+ "sms-queue trigger",
+ "SMS Queue\n" "Trigger sending messages\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ sms_queue_trigger(net->sms_queue);
+ return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_max,
+ smsqueue_max_cmd,
+ "sms-queue max-pending <1-500>",
+ "SMS Queue\n" "SMS to deliver in parallel\n" "Amount\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ sms_queue_set_max_pending(net->sms_queue, atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_clear,
+ smsqueue_clear_cmd,
+ "sms-queue clear",
+ "SMS Queue\n" "Clear the queue of pending SMS\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ sms_queue_clear(net->sms_queue);
+ return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_fail,
+ smsqueue_fail_cmd,
+ "sms-queue max-failure <1-500>",
+ "SMS Queue\n" "Maximum amount of delivery failures\n" "Amount\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ sms_queue_set_max_failure(net->sms_queue, atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_mncc_int, cfg_mncc_int_cmd,
+ "mncc-int", "Configure internal MNCC handler")
+{
+ vty->node = MNCC_INT_NODE;
+
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node mncc_int_node = {
+ MNCC_INT_NODE,
+ "%s(config-mncc-int)# ",
+ 1,
+};
+
+static const struct value_string tchf_codec_names[] = {
+ { GSM48_CMODE_SPEECH_V1, "fr" },
+ { GSM48_CMODE_SPEECH_EFR, "efr" },
+ { GSM48_CMODE_SPEECH_AMR, "amr" },
+ { 0, NULL }
+};
+
+static const struct value_string tchh_codec_names[] = {
+ { GSM48_CMODE_SPEECH_V1, "hr" },
+ { GSM48_CMODE_SPEECH_AMR, "amr" },
+ { 0, NULL }
+};
+
+static int config_write_mncc_int(struct vty *vty)
+{
+ uint16_t meas_port;
+ char *meas_host;
+ const char *meas_scenario;
+
+ meas_feed_cfg_get(&meas_host, &meas_port);
+ meas_scenario = meas_feed_scenario_get();
+
+ vty_out(vty, "mncc-int%s", VTY_NEWLINE);
+ vty_out(vty, " default-codec tch-f %s%s",
+ get_value_string(tchf_codec_names, mncc_int.def_codec[0]),
+ VTY_NEWLINE);
+ vty_out(vty, " default-codec tch-h %s%s",
+ get_value_string(tchh_codec_names, mncc_int.def_codec[1]),
+ VTY_NEWLINE);
+ if (meas_port)
+ vty_out(vty, " meas-feed destination %s %u%s",
+ meas_host, meas_port, VTY_NEWLINE);
+ if (strlen(meas_scenario) > 0)
+ vty_out(vty, " meas-feed scenario %s%s",
+ meas_scenario, VTY_NEWLINE);
+
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(mnccint_def_codec_f,
+ mnccint_def_codec_f_cmd,
+ "default-codec tch-f (fr|efr|amr)",
+ "Set default codec\n" "Codec for TCH/F\n"
+ "Full-Rate\n" "Enhanced Full-Rate\n" "Adaptive Multi-Rate\n")
+{
+ mncc_int.def_codec[0] = get_string_value(tchf_codec_names, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(mnccint_def_codec_h,
+ mnccint_def_codec_h_cmd,
+ "default-codec tch-h (hr|amr)",
+ "Set default codec\n" "Codec for TCH/H\n"
+ "Half-Rate\n" "Adaptive Multi-Rate\n")
+{
+ mncc_int.def_codec[1] = get_string_value(tchh_codec_names, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define OBSOLETE_MSG "Obsolete\n"
+/* this is just for backwards compatibility as the sms code moved into
+ * libosmocore and old config files no longer parse... */
+DEFUN_DEPRECATED(log_level_sms, log_level_sms_cmd,
+ "logging level sms (everything|debug|info|notice|error|fatal)",
+ ".HIDDEN\n" OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG
+ OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG)
+{
+ vty_out(vty, "%% 'logging level sms' is now called 'logging level "
+ "lsms', please update your config %s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+#define MEAS_STR "Measurement export related\n"
+DEFUN(mnccint_meas_feed, mnccint_meas_feed_cmd,
+ "meas-feed destination ADDR <0-65535>",
+ MEAS_STR "destination\n" "address or hostname\n" "port number\n")
+{
+ int rc;
+
+ rc = meas_feed_cfg_set(argv[0], atoi(argv[1]));
+ if (rc < 0)
+ return CMD_WARNING;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(meas_feed_scenario, meas_feed_scenario_cmd,
+ "meas-feed scenario NAME",
+ MEAS_STR "scenario\n" "Name up to 31 characters included in report\n")
+{
+ meas_feed_scenario_set(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(logging_fltr_imsi,
+ logging_fltr_imsi_cmd,
+ "logging filter imsi IMSI",
+ LOGGING_STR FILTER_STR
+ "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+ struct gsm_subscriber *vlr_subscr;
+ struct bsc_subscr *bsc_subscr;
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ struct log_target *tgt = osmo_log_vty2tgt(vty);
+ const char *imsi = argv[0];
+
+ if (!tgt)
+ return CMD_WARNING;
+
+ vlr_subscr = subscr_get_by_imsi(gsmnet->subscr_group, imsi);
+ bsc_subscr = bsc_subscr_find_by_imsi(gsmnet->bsc_subscribers, imsi);
+
+ if (!vlr_subscr && !bsc_subscr) {
+ vty_out(vty, "%%no subscriber with IMSI(%s)%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ log_set_filter_vlr_subscr(tgt, vlr_subscr);
+ log_set_filter_bsc_subscr(tgt, bsc_subscr);
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node nitb_node = {
+ NITB_NODE,
+ "%s(config-nitb)# ",
+ 1,
+};
+
+DEFUN(cfg_nitb, cfg_nitb_cmd,
+ "nitb", "Configure NITB options")
+{
+ vty->node = NITB_NODE;
+ return CMD_SUCCESS;
+}
+
+/* Note: limit on the parameter length is set by internal vty code limitations */
+DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd,
+ "subscriber-create-on-demand random <1-9999999999> <2-9999999999>",
+ "Set random parameters for a new record when a subscriber is first seen.\n"
+ "Set random parameters for a new record when a subscriber is first seen.\n"
+ "Minimum for subscriber extension\n""Maximum for subscriber extension\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ uint64_t mi = atoll(argv[0]), ma = atoll(argv[1]);
+ gsmnet->auto_create_subscr = true;
+ gsmnet->auto_assign_exten = true;
+ if (mi >= ma) {
+ vty_out(vty, "Incorrect range: %s >= %s, expected MIN < MAX%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ gsmnet->ext_min = mi;
+ gsmnet->ext_max = ma;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd,
+ "subscriber-create-on-demand [no-extension]",
+ "Make a new record when a subscriber is first seen.\n"
+ "Do not automatically assign extension to created subscribers\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->auto_create_subscr = true;
+ gsmnet->auto_assign_exten = argc ? false : true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd,
+ "no subscriber-create-on-demand",
+ NO_STR "Make a new record when a subscriber is first seen.\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->auto_create_subscr = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_assign_tmsi, cfg_nitb_assign_tmsi_cmd,
+ "assign-tmsi",
+ "Assign TMSI during Location Updating.\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->avoid_tmsi = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nitb_no_assign_tmsi, cfg_nitb_no_assign_tmsi_cmd,
+ "no assign-tmsi",
+ NO_STR "Assign TMSI during Location Updating.\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->avoid_tmsi = 1;
+ return CMD_SUCCESS;
+}
+
+static int config_write_nitb(struct vty *vty)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+ vty_out(vty, "nitb%s", VTY_NEWLINE);
+ if (!gsmnet->auto_create_subscr)
+ vty_out(vty, " no subscriber-create-on-demand%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " subscriber-create-on-demand%s%s",
+ gsmnet->auto_assign_exten ? "" : " no-extension",
+ VTY_NEWLINE);
+
+ if (gsmnet->ext_min != GSM_MIN_EXTEN || gsmnet->ext_max != GSM_MAX_EXTEN)
+ vty_out(vty, " subscriber-create-on-demand random %"PRIu64" %"
+ PRIu64"%s", gsmnet->ext_min, gsmnet->ext_max,
+ VTY_NEWLINE);
+ vty_out(vty, " %sassign-tmsi%s",
+ gsmnet->avoid_tmsi ? "no " : "", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+ osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL);
+
+ install_element_ve(&show_subscr_cmd);
+ install_element_ve(&show_subscr_cache_cmd);
+
+ install_element_ve(&sms_send_pend_cmd);
+
+ install_element_ve(&subscriber_create_cmd);
+ install_element_ve(&subscriber_send_sms_cmd);
+ install_element_ve(&subscriber_silent_sms_cmd);
+ install_element_ve(&subscriber_silent_call_start_cmd);
+ install_element_ve(&subscriber_silent_call_stop_cmd);
+ install_element_ve(&subscriber_ussd_notify_cmd);
+ install_element_ve(&subscriber_update_cmd);
+ install_element_ve(&show_stats_cmd);
+ install_element_ve(&show_smsqueue_cmd);
+ install_element_ve(&logging_fltr_imsi_cmd);
+
+ install_element(ENABLE_NODE, &ena_subscr_delete_cmd);
+ install_element(ENABLE_NODE, &ena_subscr_expire_cmd);
+ install_element(ENABLE_NODE, &ena_subscr_name_cmd);
+ install_element(ENABLE_NODE, &ena_subscr_extension_cmd);
+ install_element(ENABLE_NODE, &ena_subscr_authorized_cmd);
+ install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd);
+ install_element(ENABLE_NODE, &ena_subscr_handover_cmd);
+ install_element(ENABLE_NODE, &subscriber_purge_cmd);
+ install_element(ENABLE_NODE, &smsqueue_trigger_cmd);
+ install_element(ENABLE_NODE, &smsqueue_max_cmd);
+ install_element(ENABLE_NODE, &smsqueue_clear_cmd);
+ install_element(ENABLE_NODE, &smsqueue_fail_cmd);
+ install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd);
+ install_element(ENABLE_NODE, &meas_feed_scenario_cmd);
+
+ install_element(CONFIG_NODE, &cfg_mncc_int_cmd);
+ install_node(&mncc_int_node, config_write_mncc_int);
+ vty_install_default(MNCC_INT_NODE);
+ install_element(MNCC_INT_NODE, &mnccint_def_codec_f_cmd);
+ install_element(MNCC_INT_NODE, &mnccint_def_codec_h_cmd);
+ install_element(MNCC_INT_NODE, &mnccint_meas_feed_cmd);
+ install_element(MNCC_INT_NODE, &meas_feed_scenario_cmd);
+
+ install_element(CFG_LOG_NODE, &log_level_sms_cmd);
+ install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+
+ install_element(CONFIG_NODE, &cfg_nitb_cmd);
+ install_node(&nitb_node, config_write_nitb);
+ install_element(NITB_NODE, &cfg_nitb_subscr_create_cmd);
+ install_element(NITB_NODE, &cfg_nitb_subscr_random_cmd);
+ install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd);
+ install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd);
+ install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd);
+
+ return 0;
+}