new file mode 100644
@@ -0,0 +1,14 @@
+#ifndef _GSM_USSD_MAP_H
+#define _GSM_USSD_MAP_H
+
+#include <openbsc/gprs_gsup_client.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_ussd_map_proto.h>
+
+int ussd_map_read_cb(struct gprs_gsup_client *sup_client,
+ struct msgb *msg);
+
+int ussd_map_tx_message(struct gsm_network *net, struct ss_header *req,
+ const char *extension, uint32_t ref, const uint8_t *component_data);
+
+#endif /* _GSM_USSD_MAP_H */
new file mode 100644
@@ -0,0 +1,25 @@
+#ifndef _GSM_USSD_MAP_PROTO_H
+#define _GSM_USSD_MAP_PROTO_H
+
+#include <osmocom/gsm/gsm0480.h>
+
+
+enum {
+ FMAP_MSISDN = 0x80
+};
+
+int subscr_uss_message(struct msgb *msg,
+ struct ss_header *req,
+ const char* extension,
+ uint32_t ref,
+ const uint8_t *component_data);
+
+int rx_uss_message_parse(const uint8_t* data,
+ size_t len,
+ struct ss_header *ss,
+ uint32_t *ref,
+ char* extention,
+ size_t extention_len);
+
+
+#endif /* _GSM_USSD_MAP_PROTO_H */
@@ -56,6 +56,11 @@ struct gsm_trans {
struct gsm_sms *sms;
} sms;
+ struct {
+ uint8_t invoke_id;
+ uint8_t mo;
+ uint8_t dirty;
+ } ss;
};
};
@@ -5,6 +5,19 @@
#include <osmocom/core/msgb.h>
+#define USSD_MO 1
+#define USSD_MT 0
+
int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+int on_ussd_response(struct gsm_network *net,
+ uint32_t ref,
+ struct ss_header *reqhdr,
+ const uint8_t *component,
+ const char* extention);
+
+
+void _ussd_trans_free(struct gsm_trans *trans);
+
#endif
@@ -19,7 +19,8 @@ libmsc_a_SOURCES = auth.c \
ussd.c \
vty_interface_layer3.c \
transaction.c \
- osmo_msc.c ctrl_commands.c meas_feed.c
+ osmo_msc.c ctrl_commands.c meas_feed.c \
+ gsm_ussd_map_proto.c gsm_ussd_map.c
if BUILD_SMPP
noinst_HEADERS += smpp_smsc.h
new file mode 100644
@@ -0,0 +1,93 @@
+/* GSM USSD external MAP interface */
+
+/* (C) 2015 by Sergey Kostanbaev <sergey.kostanbaev@gmail.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/gsm_ussd_map.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/db.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_gsup_messages.h>
+#include <openbsc/gprs_gsup_client.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/ussd.h>
+
+
+int ussd_map_tx_message(struct gsm_network* net,
+ struct ss_header *req,
+ const char* extension,
+ uint32_t ref,
+ const uint8_t* component_data)
+{
+ struct msgb *msg = gprs_gsup_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ subscr_uss_message(msg, req, extension, ref, component_data);
+
+ return gprs_gsup_client_send(net->ussd_sup_client, msg);
+}
+
+
+static int ussd_map_rx_message_int(struct gsm_network *net, const uint8_t* data, size_t len)
+{
+ char extension[32] = {0};
+ uint32_t ref;
+ struct ss_header ss;
+ memset(&ss, 0, sizeof(ss));
+
+ if (rx_uss_message_parse(data, len, &ss, &ref, extension, sizeof(extension))) {
+ LOGP(DSS, LOGL_ERROR, "Can't parse SUP MAP SS message\n");
+ return -1;
+ }
+
+ LOGP(DSS, LOGL_ERROR, "Got type=0x%02x len=%d\n",
+ ss.message_type, ss.component_length);
+
+ return on_ussd_response(net, ref, &ss, data + ss.component_offset, extension);
+}
+
+static int ussd_map_rx_message(struct gprs_gsup_client *sup_client, struct msgb *msg)
+{
+ uint8_t *data = msgb_l2(msg);
+ size_t data_len = msgb_l2len(msg);
+ struct gsm_network *gsmnet = (struct gsm_network *)sup_client->data;
+
+ if (*data != GPRS_GSUP_MSGT_USSD_MAP) {
+ return -1;
+ }
+
+ return ussd_map_rx_message_int(gsmnet, data, data_len);
+}
+
+int ussd_map_read_cb(struct gprs_gsup_client *sup_client, struct msgb *msg)
+{
+ int rc;
+
+ rc = ussd_map_rx_message(sup_client, msg);
+ msgb_free(msg);
+ if (rc < 0)
+ return -1;
+
+ return rc;
+}
new file mode 100644
@@ -0,0 +1,212 @@
+/* GSM USSD external MAP protocol on pseudo TCAP */
+
+/* (C) 2015 by Sergey Kostanbaev <sergey.kostanbaev@gmail.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/gsm_ussd_map.h>
+#include <openbsc/gsm_ussd_map_proto.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/db.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_gsup_messages.h>
+#include <openbsc/gprs_gsup_client.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/ussd.h>
+
+/*
+* 0 - GPRS_GSUP_MSGT_USSD_MAP constant
+* 1 - LEN
+* 2 - message_type [ REGISTER / FACILITY / RELEASE COMPLETE ]
+* 3,4,5,6 - tid ID associated with the session
+* 7 - FMAP_MSISDN constant
+* 8 - extention_len
+* 9..x - extention
+* x+1 .. original MAP message
+*/
+
+int subscr_uss_message(struct msgb *msg,
+ struct ss_header *req,
+ const char* extension,
+ uint32_t ref,
+ const uint8_t* component_data)
+{
+ uint8_t bcd_lvlen;
+ uint8_t offset = 0;
+ uint8_t *gsup_indicator;
+
+ gsup_indicator = msgb_put(msg, 7);
+
+ /* First byte should always be GPRS_GSUP_MSGT_USSD_MAP */
+ gsup_indicator[offset++] = GPRS_GSUP_MSGT_USSD_MAP;
+ gsup_indicator[offset++] = 0; // Total length
+ gsup_indicator[offset++] = req->message_type;
+
+ gsup_indicator[offset++] = ref >> 24;
+ gsup_indicator[offset++] = ref >> 16;
+ gsup_indicator[offset++] = ref >> 8;
+ gsup_indicator[offset++] = ref;
+
+ if (extension) {
+ gsup_indicator[offset++] = FMAP_MSISDN;
+ bcd_lvlen = gsm48_encode_bcd_number(gsup_indicator + offset,
+ 32, 0, extension);
+
+ offset += bcd_lvlen;
+ msgb_put(msg, bcd_lvlen + 1);
+ }
+
+ if (component_data) {
+ msgb_put(msg, req->component_length);
+ memcpy(gsup_indicator + offset, component_data, req->component_length);
+ }
+
+ gsup_indicator[1] = offset + req->component_length - 2; //except GPRS_GSUP_MSGT_USSD_MAP and length field
+ return 0;
+#if 0
+ gsup_indicator[6] = req->component_type;
+
+ /* invokeId */
+ msgb_tlv_put(msg, GSM0480_COMPIDTAG_INVOKE_ID, 1, &req->invoke_id);
+
+ /* opCode */
+ msgb_tlv_put(msg, GSM0480_OPERATION_CODE, 1, &req->opcode);
+
+ if (req->ussd_text_len > 0) {
+ msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_len + 1, &req->ussd_text_language);
+ }
+
+ if (extension) {
+ uint8_t bcd_buf[32];
+ bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), 0,
+ extension);
+ msgb_tlv_put(msg, FMAP_MSISDN, bcd_len - 1, &bcd_buf[1]);
+ }
+
+ /* fill actual length */
+ gsup_indicator[7] = 3 + 3 + (req->ussd_text_len + 1 + 2) + (bcd_len + 2);;
+
+ /* wrap with GSM0480_CTYPE_INVOKE */
+ // gsm0480_wrap_invoke(msg, req->opcode, invoke_id);
+ // gsup_indicator = msgb_push(msgb, 1);
+ // gsup_indicator[0] = GPRS_GSUP_MSGT_MAP;
+ return 0;
+#endif
+}
+
+
+
+int rx_uss_message_parse(const uint8_t* data,
+ size_t len,
+ struct ss_header *ss,
+ uint32_t *pref,
+ char* extention,
+ size_t extention_len)
+{
+ uint8_t ext_len;
+ const uint8_t* const_data = data + 1; // Skip constant
+ uint32_t ref;
+ int total_len;
+
+ if (len < 7)
+ return -1;
+
+ /* skip GPRS_GSUP_MSGT_MAP */
+ total_len = *(const_data++);
+ ss->message_type = *(const_data++);
+
+ ref = ((uint32_t)(*(const_data++))) << 24;
+ ref |= ((uint32_t)(*(const_data++))) << 16;
+ ref |= ((uint32_t)(*(const_data++))) << 8;
+ ref |= ((uint32_t)(*(const_data++)));
+ if (pref)
+ *pref = ref;
+
+ total_len -= 4 + 1; // ref + sizeof(len)
+
+ if (*const_data == FMAP_MSISDN) {
+ ext_len = *(++const_data);
+ if (extention) {
+ gsm48_decode_bcd_number(extention,
+ extention_len,
+ const_data,
+ 0);
+ }
+ const_data += ext_len + 1;
+ total_len -= ext_len + 2; // tag FMAP_MSISDN + sizeof(len)
+ }
+
+ ss->component_offset = const_data - data;
+ ss->component_length = total_len; //data[ss->component_offset + 1];
+
+ return 0;
+#if 0
+ ss->component_type = *(++const_data);
+
+ /* skip full len and move to component id */
+ const_data += 2;
+
+ if (*const_data != GSM0480_COMPIDTAG_INVOKE_ID) {
+ return -1;
+ }
+ const_data += 2;
+ ss->invoke_id = *const_data;
+ const_data++;
+
+ //
+ if (*const_data != GSM0480_OPERATION_CODE) {
+ return -1;
+ }
+ const_data += 2;
+ ss->opcode = *const_data;
+ const_data++;
+
+
+ while (const_data - data < len) {
+ uint8_t len;
+ switch (*const_data) {
+ case ASN1_OCTET_STRING_TAG:
+ ss->ussd_text_len = len = (*(++const_data) - 1);
+ ss->ussd_text_language = *(++const_data);
+ memcpy(ss->ussd_text,
+ ++const_data,
+ (len > MAX_LEN_USSD_STRING) ? MAX_LEN_USSD_STRING : len);
+ const_data += len;
+ break;
+
+ case FMAP_MSISDN:
+ len = *(++const_data);
+ gsm48_decode_bcd_number(extention,
+ extention_len,
+ const_data,
+ 0);
+ const_data += len + 1;
+ break;
+ default:
+ DEBUGP(DSS, "Unknown code: %d\n", *const_data);
+ return -1;
+ }
+ }
+
+ return 0;
+#endif
+}
@@ -25,6 +25,7 @@
#include <osmocom/core/talloc.h>
#include <openbsc/gsm_subscriber.h>
#include <openbsc/gsm_04_08.h>
+#include <openbsc/ussd.h>
#include <openbsc/mncc.h>
#include <openbsc/paging.h>
#include <openbsc/osmo_msc.h>
@@ -96,6 +97,9 @@ void trans_free(struct gsm_trans *trans)
case GSM48_PDISC_SMS:
_gsm411_sms_trans_free(trans);
break;
+ case GSM48_PDISC_NC_SS:
+ _ussd_trans_free(trans);
+ break;
}
if (trans->paging_request) {
@@ -33,9 +33,18 @@
#include <openbsc/gsm_subscriber.h>
#include <openbsc/debug.h>
#include <openbsc/osmo_msc.h>
+#include <openbsc/gsm_ussd_map.h>
#include <openbsc/ussd.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <openbsc/transaction.h>
+
+/* Last uniq generated session id */
+static uint32_t s_uniq_ussd_sessiod_id = 0;
+
+/* Forward declaration of USSD handler for USSD MAP interface */
+static int handle_rcv_ussd_sup(struct gsm_subscriber_connection *conn, struct msgb *msg);
/* Declarations of USSD strings to be recognised */
const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
@@ -55,6 +64,9 @@ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg)
char request_string[MAX_LEN_USSD_STRING + 1];
struct gsm48_hdr *gh;
+ if (conn->subscr->group->net->ussd_sup_client)
+ return handle_rcv_ussd_sup(conn, msg);
+
memset(&req, 0, sizeof(req));
memset(&reqhdr, 0, sizeof(reqhdr));
gh = msgb_l3(msg);
@@ -136,3 +148,211 @@ static int send_own_number(struct gsm_subscriber_connection *conn,
gsm0480_compose_ussd_component(&rss),
&rssh);
}
+
+
+static int ussd_sup_send_reject(struct gsm_network *conn, uint32_t ref)
+{
+ struct ss_header rej;
+ rej.message_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+ rej.component_length = 0;
+
+ return ussd_map_tx_message(conn, &rej, NULL, ref, NULL);
+}
+
+/* Callback from USSD MAP interface */
+int on_ussd_response(struct gsm_network *net,
+ uint32_t ref,
+ struct ss_header *reqhdr,
+ const uint8_t* component,
+ const char *extention)
+{
+ struct gsm_trans *trans = trans_find_by_callref(net, ref);
+ int rc = 0;
+ struct msgb *msg;
+ uint8_t *ptr8;
+ struct ss_header ssrep = *reqhdr;
+
+ switch (reqhdr->message_type) {
+ case GSM0480_MTYPE_REGISTER:
+ DEBUGP(DSS, "Network originated USSD messages isn't supported yet!\n");
+
+ ussd_sup_send_reject(net, ref);
+ return 0;
+
+ case GSM0480_MTYPE_FACILITY:
+ case GSM0480_MTYPE_RELEASE_COMPLETE:
+ if (!trans) {
+ DEBUGP(DSS, "No session was found for ref: %d!\n",
+ ref);
+
+ ussd_sup_send_reject(net, ref);
+ return 0;
+ }
+ break;
+ default:
+ DEBUGP(DSS, "Unknown message type 0x%02x\n", reqhdr->message_type);
+ ussd_sup_send_reject(net, ref);
+ return 0;
+ }
+
+ msg = gsm48_msgb_alloc();
+ ptr8 = msgb_put(msg, 0);
+
+ memcpy(ptr8, component, reqhdr->component_length);
+ msgb_put(msg, reqhdr->component_length);
+
+ ssrep.transaction_id = (trans->transaction_id << 4) ^ 0x80;
+ rc = gsm0480_send_component(trans->conn, msg, &ssrep);
+
+ if (reqhdr->message_type == GSM0480_MTYPE_RELEASE_COMPLETE) {
+ struct gsm_subscriber_connection* conn = trans->conn;
+
+ trans_free(trans);
+ msc_release_connection(conn);
+ }
+
+ return rc;
+}
+
+static int get_invoke_id(const uint8_t* data, uint8_t len, uint8_t* pinvoke_id)
+{
+ /* 0: CTYPE tag
+ * 1..x: CTYPE len
+ * x: INVOKE_ID tag
+ * x+1: INVOKE_ID len
+ * x+2: INVOKE_ID value
+ */
+ if (len < 5)
+ return 0;
+
+ unsigned inv_offset = 2;
+ switch (data[0]) {
+ case GSM0480_CTYPE_INVOKE:
+ case GSM0480_CTYPE_RETURN_RESULT:
+ if (data[1] > 0x80)
+ inv_offset += data[1] & 0x7f;
+ if (inv_offset + 2 >= len)
+ return 0;
+ if (data[inv_offset] != GSM0480_COMPIDTAG_INVOKE_ID)
+ return 0;
+ *pinvoke_id = data[inv_offset + 2];
+ return 1;
+ }
+ return 0;
+}
+
+/* Handler function common to all mobile-originated USSDs in case if USSD MAP enabled */
+static int handle_rcv_ussd_sup(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ int rc = 0;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct ss_header reqhdr;
+ struct gsm_trans *trans = NULL;
+ uint8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */
+ uint8_t invoke_id = 0;
+
+ if (!conn->subscr)
+ return -EIO;
+
+ memset(&reqhdr, 0, sizeof(reqhdr));
+
+ DEBUGP(DSS, "handle ussd tid=%d: %s\n", transaction_id, msgb_hexdump(msg));
+ trans = trans_find_by_id(conn, GSM48_PDISC_NC_SS, transaction_id);
+
+ rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &reqhdr);
+ if (!rc) {
+ DEBUGP(DSS, "Incorrect SS header\n");
+ if (!trans) {
+ goto release_conn;
+ }
+
+ /* don't know how to process */
+ goto failed_transaction;
+ }
+
+
+ switch (reqhdr.message_type) {
+ case GSM0480_MTYPE_REGISTER:
+ if (trans) {
+ /* we already have a transaction, ignore this message */
+ goto release_conn;
+ }
+ if (!get_invoke_id(gh->data + reqhdr.component_offset,
+ reqhdr.component_length,
+ &invoke_id)) {
+ DEBUGP(DSS, "Incorrect InvokeID in transaction\n");
+ goto release_conn;
+ }
+
+ trans = trans_alloc(conn->bts->network, conn->subscr,
+ GSM48_PDISC_NC_SS,
+ transaction_id, s_uniq_ussd_sessiod_id++);
+ if (!trans) {
+ DEBUGP(DSS, "Failed to create new ussd transaction\n");
+ goto transaction_not_found;
+ }
+
+ trans->conn = conn;
+ trans->ss.invoke_id = invoke_id;
+ trans->ss.mo = 1;
+ trans->ss.dirty = 1;
+ break;
+
+ case GSM0480_MTYPE_FACILITY:
+ if (!trans) {
+ DEBUGP(DSS, "No session found tid=%d\n",
+ transaction_id);
+
+ if (!get_invoke_id(gh->data + reqhdr.component_offset,
+ reqhdr.component_length,
+ &invoke_id)) {
+ DEBUGP(DSS, "Incorrect InvokeID in transaction\n");
+ goto release_conn;
+ }
+
+ goto transaction_not_found;
+ }
+ break;
+
+ case GSM0480_MTYPE_RELEASE_COMPLETE:
+ if (!trans) {
+ DEBUGP(DSS, "RELEASE_COMPLETE to non-existing transaction!\n");
+ goto release_conn;
+ }
+
+ trans_free(trans);
+ goto release_conn;
+ }
+
+ rc = ussd_map_tx_message(conn->subscr->group->net, &reqhdr,
+ conn->subscr->extension, trans->callref,
+ gh->data + reqhdr.component_offset);
+ if (rc) {
+ /* do not send reject if we failed with the message */
+ trans->ss.dirty = 0;
+
+ DEBUGP(DSS, "Unable tp send uss over sup reason: %d\n", rc);
+ goto failed_transaction;
+ }
+ return 0;
+
+failed_transaction:
+ trans_free(trans);
+
+transaction_not_found:
+ gsm0480_send_ussd_reject(conn, invoke_id, transaction_id);
+
+release_conn:
+ msc_release_connection(conn);
+ return rc;
+}
+
+void _ussd_trans_free(struct gsm_trans *trans)
+{
+ if (trans->ss.dirty) {
+ trans->ss.dirty = 0;
+
+ ussd_sup_send_reject(trans->net, trans->callref);
+ }
+}
+
From: Sergey Kostanbaev <Sergey.Kostanbaev@fairwaves.co> --- openbsc/include/openbsc/gsm_ussd_map.h | 14 ++ openbsc/include/openbsc/gsm_ussd_map_proto.h | 25 +++ openbsc/include/openbsc/transaction.h | 5 + openbsc/include/openbsc/ussd.h | 13 ++ openbsc/src/libmsc/Makefile.am | 3 +- openbsc/src/libmsc/gsm_ussd_map.c | 93 +++++++++++ openbsc/src/libmsc/gsm_ussd_map_proto.c | 212 ++++++++++++++++++++++++++ openbsc/src/libmsc/transaction.c | 4 + openbsc/src/libmsc/ussd.c | 220 +++++++++++++++++++++++++++ 9 files changed, 588 insertions(+), 1 deletion(-) create mode 100644 openbsc/include/openbsc/gsm_ussd_map.h create mode 100644 openbsc/include/openbsc/gsm_ussd_map_proto.h create mode 100644 openbsc/src/libmsc/gsm_ussd_map.c create mode 100644 openbsc/src/libmsc/gsm_ussd_map_proto.c