diff options
author | Sergey.Kostanbaev <Sergey.Kostanbaev@gmail.com> | 2016-02-09 20:21:08 +0300 |
---|---|---|
committer | Ivan Kluchnikov <kluchnikovi@gmail.com> | 2017-02-07 18:59:54 +0300 |
commit | d4839fe14a0d060933f0006d10dc932375a7c7d6 (patch) | |
tree | a640aa5741fa5f75646e6b3e779957639eb8294e | |
parent | db0e216845a7859bf878a891e2a210dbef6395df (diff) |
manual merge SS from sup-ussd-on-master-ss-wip
-rw-r--r-- | openbsc/configure.ac | 13 | ||||
-rw-r--r-- | openbsc/include/openbsc/debug.h | 1 | ||||
-rw-r--r-- | openbsc/include/openbsc/gsm_04_80.h | 21 | ||||
-rw-r--r-- | openbsc/include/openbsc/gsm_ussd_map.h | 14 | ||||
-rw-r--r-- | openbsc/include/openbsc/gsm_ussd_map_proto.h | 25 | ||||
-rw-r--r-- | openbsc/include/openbsc/transaction.h | 5 | ||||
-rw-r--r-- | openbsc/include/openbsc/ussd.h | 9 | ||||
-rw-r--r-- | openbsc/src/Makefile.am | 1 | ||||
-rw-r--r-- | openbsc/src/libmsc/Makefile.am | 2 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_04_80.c | 167 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_sup.c | 7 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_ussd_map.c | 93 | ||||
-rw-r--r-- | openbsc/src/libmsc/gsm_ussd_map_proto.c | 212 | ||||
-rw-r--r-- | openbsc/src/libmsc/ussd.c | 517 | ||||
-rw-r--r-- | openbsc/src/libmsc/vty_interface_layer3.c | 10 | ||||
-rw-r--r-- | openbsc/src/reg-proxy/Makefile.am | 13 | ||||
-rw-r--r-- | openbsc/src/reg-proxy/sup.c | 8 | ||||
-rw-r--r-- | openbsc/src/ussd-proxy/Makefile.am | 19 | ||||
-rw-r--r-- | openbsc/src/ussd-proxy/ussd_proxy.c (renamed from openbsc/src/reg-proxy/ussd_proxy.c) | 736 | ||||
-rw-r--r-- | openbsc/tests/Makefile.am | 1 |
20 files changed, 1251 insertions, 623 deletions
diff --git a/openbsc/configure.ac b/openbsc/configure.ac index c31d36eaf..179e56f97 100644 --- a/openbsc/configure.ac +++ b/openbsc/configure.ac @@ -211,6 +211,18 @@ AC_MSG_CHECKING([whether to enable VTY/CTRL tests]) AC_MSG_RESULT([$enable_ext_tests]) AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes") + +# Enable/disable ussd_proxy utility +AC_ARG_ENABLE([ussd_proxy], [AS_HELP_STRING([--enable-ussd-proxy], [Build the USSD MAP SUP to SIP proxy])], + [osmo_ac_build_ussd_proxy="$enableval"],[osmo_ac_build_ussd_proxy="no"]) +if test "$osmo_ac_build_ussd_proxy" = "yes" ; then + PKG_CHECK_MODULES(LIBSOFIA_SIP_UA, sofia-sip-ua >= 1.10) + AC_DEFINE(BUILD_USSD_PROXY, 1, [Define if we want to build ussd_proxy]) +fi +AM_CONDITIONAL(BUILD_USSD_PROXY, test "x$osmo_ac_build_ussd_proxy" = "xyes") +AC_SUBST(osmo_ac_build_smpp) + + dnl Generate the output AM_CONFIG_HEADER(bscconfig.h) @@ -259,6 +271,7 @@ AC_OUTPUT( tests/slhc/Makefile tests/v42bis/Makefile tests/nanobts_omlattr/Makefile + tests/ussd/Makefile doc/Makefile doc/examples/Makefile Makefile) diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h index d255d29e9..45842dcd7 100644 --- a/openbsc/include/openbsc/debug.h +++ b/openbsc/include/openbsc/debug.h @@ -39,6 +39,7 @@ enum { DSUA, DV42BIS, DSUP, + DSS, Debug_LastEntry, }; diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h index 68de65a0f..a092938ca 100644 --- a/openbsc/include/openbsc/gsm_04_80.h +++ b/openbsc/include/openbsc/gsm_04_80.h @@ -7,18 +7,17 @@ struct gsm_subscriber_connection; -int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, - int response_text_len, - uint8_t response_lang, - const char* response_text, - const struct ussd_request *req, - uint8_t code, - uint8_t ctype, - uint8_t mtype); +int gsm0480_send_component(struct gsm_subscriber_connection *conn, + struct msgb *msg, + struct ss_header* reqhdr); + int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, - const struct msgb *msg, - const struct ussd_request *request); + uint8_t invoke_id, + uint8_t transaction_id); + +struct msgb *gsm0480_compose_ussd_component(struct ss_request* req); + + int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text); diff --git a/openbsc/include/openbsc/gsm_ussd_map.h b/openbsc/include/openbsc/gsm_ussd_map.h new file mode 100644 index 000000000..72798b24f --- /dev/null +++ b/openbsc/include/openbsc/gsm_ussd_map.h @@ -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 */ diff --git a/openbsc/include/openbsc/gsm_ussd_map_proto.h b/openbsc/include/openbsc/gsm_ussd_map_proto.h new file mode 100644 index 000000000..7faa5b85f --- /dev/null +++ b/openbsc/include/openbsc/gsm_ussd_map_proto.h @@ -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 */ diff --git a/openbsc/include/openbsc/transaction.h b/openbsc/include/openbsc/transaction.h index 9a87d04e4..6890f14ae 100644 --- a/openbsc/include/openbsc/transaction.h +++ b/openbsc/include/openbsc/transaction.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; }; }; diff --git a/openbsc/include/openbsc/ussd.h b/openbsc/include/openbsc/ussd.h index eee0b9a0c..b5b073ae9 100644 --- a/openbsc/include/openbsc/ussd.h +++ b/openbsc/include/openbsc/ussd.h @@ -11,6 +11,13 @@ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg); -int on_ussd_response(const struct ss_request* req, const char* extention); +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 diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am index cb6041cc2..0f50d7d97 100644 --- a/openbsc/src/Makefile.am +++ b/openbsc/src/Makefile.am @@ -45,6 +45,7 @@ SUBDIRS += \ ipaccess \ gprs \ reg-proxy \ + ussd-proxy \ $(NULL) # Conditional Programs diff --git a/openbsc/src/libmsc/Makefile.am b/openbsc/src/libmsc/Makefile.am index f9bcf2f37..c06b2960d 100644 --- a/openbsc/src/libmsc/Makefile.am +++ b/openbsc/src/libmsc/Makefile.am @@ -47,6 +47,8 @@ libmsc_a_SOURCES = \ osmo_msc.c \ ctrl_commands.c \ meas_feed.c \ + gsm_ussd_map_proto.c \ + gsm_ussd_map.c \ $(NULL) if BUILD_SMPP diff --git a/openbsc/src/libmsc/gsm_04_80.c b/openbsc/src/libmsc/gsm_04_80.c index a8cf3e9bf..716fe751f 100644 --- a/openbsc/src/libmsc/gsm_04_80.c +++ b/openbsc/src/libmsc/gsm_04_80.c @@ -39,7 +39,7 @@ #include <osmocom/core/msgb.h> #include <osmocom/gsm/tlv.h> - +/* This function can handle ASN1 length up to 255 which is enough for USSD */ static inline unsigned char *msgb_wrap_with_ASN1_TL(struct msgb *msgb, uint8_t tag) { uint16_t origlen = msgb->len; @@ -75,56 +75,135 @@ static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, return data; } +static inline unsigned char *msgb_wrap_with_L(struct msgb *msgb) +{ + uint8_t *data = msgb_push(msgb, 1); + + data[0] = msgb->len - 1; + 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, - int response_text_len, - uint8_t response_lang, - const char *response_text, - const struct ussd_request *req, - uint8_t code, - uint8_t ctype, - uint8_t mtype) +/* Compose universial USSD packet invoke/return_result payload */ +struct msgb *gsm0480_compose_ussd_component(struct ss_request* req) { struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD RSP"); + uint8_t *ptr8; + + /* First put the payload text into the message */ + ptr8 = msgb_put(msg, 0); + + memcpy(ptr8, req->ussd_text, req->ussd_text_len); + msgb_put(msg, req->ussd_text_len); + + /* Then wrap it as an Octet String */ + msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG); + + /* Pre-pend the DCS octet string */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_language); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); + + if (req->component_type == GSM0480_CTYPE_RETURN_RESULT) { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, req->opcode); + + /* Wrap the operation code and IA5 string as a sequence */ + msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + } else if (req->component_type == GSM0480_CTYPE_INVOKE) { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, req->opcode); + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + } else { + abort(); + } + + /* Wrap this up as an Invoke or a Return Result component */ + msgb_wrap_with_ASN1_TL(msg, req->component_type); + return msg; +} + +#ifndef NO_GSM0480_SEND_FUNC + +int gsm0480_send_component(struct gsm_subscriber_connection *conn, + struct msgb *msg, + struct ss_header* reqhdr) +{ +#if 0 + struct msgb *msg = gsm48_msgb_alloc(); struct gsm48_hdr *gh; uint8_t *ptr8; - int response_len; ptr8 = msgb_put(msg, 0); - if (response_text_len < 0) { - /* First put the payload text into the message */ - gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len); - msgb_put(msg, response_len); - response_lang = 0x0F; + memcpy(ptr8, component, reqhdr->component_length); + msgb_put(msg, reqhdr->component_length); +#endif + struct gsm48_hdr *gh; + + if (reqhdr->message_type == GSM0480_MTYPE_REGISTER || + reqhdr->message_type == GSM0480_MTYPE_RELEASE_COMPLETE) { + /* Wrap the component in a Facility message, it's not ASN1 */ + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + } else if (reqhdr->message_type == GSM0480_MTYPE_FACILITY) { + /* For GSM0480_MTYPE_FACILITY it's LV not TLV */ + msgb_wrap_with_L(msg); } else { - memcpy(ptr8, response_text, response_text_len); - msgb_put(msg, response_text_len); + abort(); } + /* And finally pre-pend the L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | reqhdr->transaction_id + | (1<<7); /* TI direction = 1 */ + gh->msg_type = reqhdr->message_type; + + DEBUGP(DSS, "Sending SS to mobile: %s\n", msgb_hexdump(msg)); + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +#if 0 +/* Compose universial SS packet except Reject opcodes */ +int gsm0480_send_ussd(struct gsm_subscriber_connection *conn, + struct ss_request* req) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + uint8_t *ptr8; + + /* First put the payload text into the message */ + ptr8 = msgb_put(msg, 0); + + memcpy(ptr8, req->ussd_text, req->ussd_text_len); + msgb_put(msg, req->ussd_text_len); + /* Then wrap it as an Octet String */ msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG); /* Pre-pend the DCS octet string */ - msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, response_lang); + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_language); /* Then wrap these as a Sequence */ msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); - if (ctype == GSM0480_CTYPE_RETURN_RESULT) { + if (req->component_type == GSM0480_CTYPE_RETURN_RESULT) { /* Pre-pend the operation code */ - msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, code); + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, req->opcode); /* Wrap the operation code and IA5 string as a sequence */ msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); /* Pre-pend the invoke ID */ msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); - } else if (ctype == GSM0480_CTYPE_INVOKE) { + } else if (req->component_type == GSM0480_CTYPE_INVOKE) { /* Pre-pend the operation code */ - msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, code); + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, req->opcode); /* Pre-pend the invoke ID */ msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); @@ -132,16 +211,16 @@ int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, abort(); } - /* Wrap this up as a Return Result component */ - msgb_wrap_with_ASN1_TL(msg, ctype); + /* Wrap this up as an Invoke or a Return Result component */ + msgb_wrap_with_ASN1_TL(msg, req->component_type); - if (mtype == GSM0480_MTYPE_REGISTER || - mtype == GSM0480_MTYPE_RELEASE_COMPLETE) { + if (req->message_type == GSM0480_MTYPE_REGISTER || + req->message_type == GSM0480_MTYPE_RELEASE_COMPLETE) { /* Wrap the component in a Facility message, it's not ASN1 */ msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); - } else if (mtype == GSM0480_MTYPE_FACILITY) { - uint8_t *data = msgb_push(msg, 1); - data[0] = msg->len - 1; + } else if (req->message_type == GSM0480_MTYPE_FACILITY) { + /* For GSM0480_MTYPE_FACILITY it's LV not TLV */ + msgb_wrap_with_L(msg); } else { abort(); } @@ -149,42 +228,48 @@ int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, /* 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 */ + | (1<<7); /* TI direction = 1 */ + gh->msg_type = req->message_type; - gh->msg_type = mtype; - - DEBUGP(DSUP, "Sending USSD to mobile: %s\n", msgb_hexdump(msg)); + DEBUGP(DSS, "Sending USSD to mobile: %s\n", msgb_hexdump(msg)); return gsm0808_submit_dtap(conn, msg, 0, 0); } +#endif int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, - const struct ussd_request *req) + uint8_t invoke_id, + uint8_t transaction_id) { struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD REJ"); - struct gsm48_hdr *gh; + struct ss_header ssh; /* 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); + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, invoke_id); /* Wrap this up as a Reject component */ msgb_wrap_with_ASN1_TL(msg, GSM0480_CTYPE_REJECT); + /* Prepare data for L3 header */ + ssh.transaction_id = transaction_id; + ssh.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + return gsm0480_send_component(conn, msg, &ssh); +#if 0 /* 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->proto_discr |= transaction_id | (1<<7); /* TI direction = 1 */ gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; return gsm0808_submit_dtap(conn, msg, 0, 0); +#endif } int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text) @@ -202,3 +287,5 @@ int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) return -1; return gsm0808_submit_dtap(conn, msg, 0, 0); } + +#endif diff --git a/openbsc/src/libmsc/gsm_sup.c b/openbsc/src/libmsc/gsm_sup.c index f9edec352..9dc8f191e 100644 --- a/openbsc/src/libmsc/gsm_sup.c +++ b/openbsc/src/libmsc/gsm_sup.c @@ -32,6 +32,7 @@ #include <openbsc/gprs_utils.h> #include <openbsc/ussd.h> +#if 0 enum { FMAP_MSISDN = 0x80 }; @@ -171,7 +172,7 @@ static int rx_uss_message(const uint8_t* data, size_t len) return on_ussd_response(&ss, extention); } - +#endif static int subscr_tx_sup_message(struct gprs_gsup_client *sup_client, struct gsm_subscriber *subscr, @@ -438,11 +439,11 @@ static int subscr_rx_sup_message(struct gprs_gsup_client *sup_client, struct msg struct gprs_gsup_message gsup_msg = {0}; struct gsm_subscriber *subscr; - +#if 0 if (*data == GPRS_GSUP_MSGT_MAP) { return rx_uss_message(data, data_len); } - +#endif rc = gprs_gsup_decode(data, data_len, &gsup_msg); if (rc < 0) { LOGP(DSUP, LOGL_ERROR, diff --git a/openbsc/src/libmsc/gsm_ussd_map.c b/openbsc/src/libmsc/gsm_ussd_map.c new file mode 100644 index 000000000..7ca84b133 --- /dev/null +++ b/openbsc/src/libmsc/gsm_ussd_map.c @@ -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; +} diff --git a/openbsc/src/libmsc/gsm_ussd_map_proto.c b/openbsc/src/libmsc/gsm_ussd_map_proto.c new file mode 100644 index 000000000..1d48efbd5 --- /dev/null +++ b/openbsc/src/libmsc/gsm_ussd_map_proto.c @@ -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 +} diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index b31f3e1a0..ac032439e 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -33,378 +33,341 @@ #include <openbsc/gsm_subscriber.h> #include <openbsc/debug.h> #include <openbsc/osmo_msc.h> -#include <openbsc/gsm_sup.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> -struct gsm_ussd { - struct llist_head ussqueue; +/* Last uniq generated session id */ +static uint32_t s_uniq_ussd_sessiod_id = 0; - uint8_t uniq_id; /**< System wide uniq ID */ +/* Forward declaration of USSD handler for USSD MAP interface */ +static int handle_rcv_ussd_sup(struct gsm_subscriber_connection *conn, struct msgb *msg); - uint8_t invoke_id; - uint8_t transaction_id; - - uint8_t mobile_originated; +/* Declarations of USSD strings to be recognised */ +const char USSD_TEXT_OWN_NUMBER[] = "*#100#"; - struct gsm_subscriber_connection *conn; -}; +/* Forward declarations of network-specific handler functions */ +static int send_own_number(struct gsm_subscriber_connection *conn, + const struct ss_header *reqhdr, + const struct ss_request *req); -static unsigned s_ussd_open_sessions = 0; -static uint64_t s_uniq_ussd_sessiod_id = 0; -static LLIST_HEAD(s_active_ussd_sessions); -static struct llist_head *get_active_ussd_sessions(void) +/* Entrypoint - handler function common to all mobile-originated USSDs */ +int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) { - return &s_active_ussd_sessions; -} + int rc; + struct ss_header reqhdr; + struct ss_request req; + 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); -static struct gsm_ussd* ussd_session_alloc(struct gsm_subscriber_connection* conn, - uint8_t tid, - uint8_t mo) -{ - struct gsm_network* net = conn->bts->network; - struct gsm_ussd* m = talloc_zero(net, struct gsm_ussd); - if (!m) - return NULL; - - m->conn = conn; - m->uniq_id = s_uniq_ussd_sessiod_id++; - m->transaction_id = tid; - m->mobile_originated = mo; - ++s_ussd_open_sessions; - - INIT_LLIST_HEAD(&m->ussqueue); - llist_add_tail(&m->ussqueue, &s_active_ussd_sessions); - - DEBUGP(DMM, "Alloc USSD session: %d (open: %d)\n", m->uniq_id, s_ussd_open_sessions); - return m; -} + memset(&req, 0, sizeof(req)); + memset(&reqhdr, 0, sizeof(reqhdr)); + gh = msgb_l3(msg); + rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &reqhdr); + if (!rc) { + DEBUGP(DSS, "Incorrect SS header\n"); + msc_release_connection(conn); + return rc; + } -static void ussd_session_free(struct gsm_ussd* s) -{ - --s_ussd_open_sessions; - DEBUGP(DMM, "Free USSD session: %d (open: %d)\n", s->uniq_id, s_ussd_open_sessions); - llist_del(&s->ussqueue); - talloc_free(s); -} + rc = gsm0480_parse_ss_facility(gh->data + reqhdr.component_offset, + reqhdr.component_length, + &req); + if (!rc) { + DEBUGP(DSS, "Unhandled SS\n"); + // TODO req.invoke_id may not be set!!! + rc = gsm0480_send_ussd_reject(conn, req.invoke_id, reqhdr.transaction_id); + msc_release_connection(conn); + return rc; + } -static struct gsm_ussd* get_by_uniq_id(uint8_t uniq_id) -{ - struct gsm_ussd* c; - llist_for_each_entry(c, get_active_ussd_sessions(), ussqueue) { - if (c->uniq_id == uniq_id) { - DEBUGP(DMM, "uniq_id %d has %s extention\n", - uniq_id, c->conn->subscr->extension); - return c; - } + if (reqhdr.message_type == GSM0480_MTYPE_RELEASE_COMPLETE) + return 0; + + if (reqhdr.message_type != GSM0480_MTYPE_REGISTER || + req.component_type != GSM0480_CTYPE_INVOKE || + req.opcode != GSM0480_OP_CODE_PROCESS_USS_REQ || + req.ussd_text_language != 0x0f) + { + DEBUGP(DSS, "Unexpected SS\n"); + rc = gsm0480_send_ussd_reject(conn, req.invoke_id, reqhdr.transaction_id); + msc_release_connection(conn); + return rc; } - DEBUGP(DMM, "uniq_id %d hasn't been found\n", uniq_id); - return NULL; + gsm_7bit_decode_n_ussd(request_string, MAX_LEN_USSD_STRING, req.ussd_text, req.ussd_text_len); + + if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)request_string)) { + DEBUGP(DSS, "USSD: Own number requested\n"); + rc = send_own_number(conn, &reqhdr, &req); + } else { + DEBUGP(DSS, "Unhandled USSD %s\n", request_string); + rc = gsm0480_send_ussd_reject(conn, req.invoke_id, reqhdr.transaction_id); + } + + /* check if we can release it */ + msc_release_connection(conn); + return rc; } -static struct gsm_ussd* get_by_iid(struct gsm_subscriber_connection *conn, uint8_t invoke_id) +/* A network-specific handler function */ +static int send_own_number(struct gsm_subscriber_connection *conn, + const struct ss_header *reqhdr, + const struct ss_request *req) { - struct gsm_ussd* c; - llist_for_each_entry(c, get_active_ussd_sessions(), ussqueue) { - if (c->conn == conn && c->invoke_id == invoke_id) { - DEBUGP(DMM, "invoke_id %d has %s extention\n", - invoke_id, c->conn->subscr->extension); - return c; - } - } + struct ss_request rss; + struct ss_header rssh; - DEBUGP(DMM, "invoke_id %d hasn't been found\n", invoke_id); - return NULL; + char *own_number = conn->subscr->extension; + char response_string[GSM_EXTENSION_LENGTH + 20]; + int response_len; + + /* Need trailing CR as EOT character */ + snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number); + + memset(&rss, 0, sizeof(rss)); + gsm_7bit_encode_n_ussd(rss.ussd_text, MAX_LEN_USSD_STRING, response_string, &response_len); + rss.ussd_text_len = response_len; + rss.ussd_text_language = 0x0f; + + rss.component_type = GSM0480_CTYPE_RETURN_RESULT; + rss.invoke_id = req->invoke_id; + rss.opcode = GSM0480_OP_CODE_PROCESS_USS_REQ; + + rssh.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + rssh.transaction_id = reqhdr->transaction_id; + + return gsm0480_send_component(conn, + gsm0480_compose_ussd_component(&rss), + &rssh); } -static struct gsm_ussd* get_by_tid(struct gsm_subscriber_connection *conn, uint8_t transaction_id) + +static int ussd_sup_send_reject(struct gsm_network *conn, uint32_t ref) { - struct gsm_ussd* c; - llist_for_each_entry(c, get_active_ussd_sessions(), ussqueue) { - if (c->conn == conn && c->transaction_id == transaction_id) { - DEBUGP(DMM, "transaction_id %d has %s extention\n", - transaction_id, c->conn->subscr->extension); - return c; - } - } + struct ss_header rej; + rej.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + rej.component_length = 0; - DEBUGP(DMM, "transaction_id %d hasn't been found\n", transaction_id); - return NULL; +#if 0 + rej.component_type = GSM0480_CTYPE_REJECT; + rej.invoke_id = invokeid; + rej.opcode = opcode; + rej.ussd_text_len = 0; +#endif + return ussd_map_tx_message(conn, &rej, NULL, ref, NULL); } -// From SUP -int on_ussd_response(const struct ss_request *req, const char *extention) +/* 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 ussd_request ussd_req; - struct gsm_ussd* ussdq; - memset(&ussd_req, 0, sizeof(ussd_req)); + struct gsm_trans *trans = trans_find_by_callref(net, ref); int rc = 0; + struct msgb *msg; + uint8_t *ptr8; - switch (req->message_type) { + switch (reqhdr->message_type) { case GSM0480_MTYPE_REGISTER: - DEBUGP(DMM, "Network originated USSD messages isn't supported yet!\n"); + DEBUGP(DSS, "Network originated USSD messages isn't supported yet!\n"); - //TODO Send to sup rejection + ussd_sup_send_reject(net, ref); return 0; case GSM0480_MTYPE_FACILITY: case GSM0480_MTYPE_RELEASE_COMPLETE: - // FIXME add uinq_id field - ussdq = get_by_uniq_id(req->invoke_id); - if (!ussdq) { - DEBUGP(DMM, "No session was found for uniq_id: %d!\n", - req->invoke_id); - // TODO SUP Reject + if (!trans) { + DEBUGP(DSS, "No session was found for ref: %d!\n", + ref); + + ussd_sup_send_reject(net, ref); return 0; } break; default: - DEBUGP(DMM, "Unknown message type 0x%02x\n", req->message_type); - // TODO SUP Reject + DEBUGP(DSS, "Unknown message type 0x%02x\n", reqhdr->message_type); + ussd_sup_send_reject(net, ref); return 0; } - ussd_req.transaction_id = ussdq->transaction_id; - ussd_req.invoke_id = ussdq->invoke_id; +#if 0 + req->invoke_id = trans->ss.invoke_id; + req->transaction_id = (trans->transaction_id << 4) ^ 0x80; if (req->component_type != GSM0480_CTYPE_REJECT) { - rc = gsm0480_send_ussd_response(ussdq->conn, - NULL, - (req->ussd_text_language == 0x80) ? -1 : req->ussd_text_len, - req->ussd_text_language, - (const char *)req->ussd_text, - &ussd_req, - req->opcode, - req->component_type, - req->message_type); + rc = gsm0480_send_ussd(trans->conn, req); } else { - rc = gsm0480_send_ussd_reject(ussdq->conn, NULL, &ussd_req); + rc = gsm0480_send_ussd_reject(trans->conn, req); } +#endif + msg = gsm48_msgb_alloc(); + ptr8 = msgb_put(msg, 0); + + memcpy(ptr8, component, reqhdr->component_length); + msgb_put(msg, reqhdr->component_length); + + rc = gsm0480_send_component(trans->conn, msg, reqhdr); - if (req->message_type == GSM0480_MTYPE_RELEASE_COMPLETE) { - msc_release_connection(ussdq->conn); - ussd_session_free(ussdq); + 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 ussd_sup_send_reject(struct gsm_subscriber_connection *conn, - uint8_t uniq_id, uint8_t opcode) +static int get_invoke_id(const uint8_t* data, uint8_t len, uint8_t* pinvoke_id) { - struct ss_request rej; - rej.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; - rej.component_type = GSM0480_CTYPE_REJECT; - rej.invoke_id = uniq_id; - rej.opcode = opcode; - rej.ussd_text_len = 0; + /* 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; - return subscr_tx_uss_message(&rej, conn->subscr); + 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; } -/* Entrypoint - handler function common to all mobile-originated USSDs */ -int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) +/* 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; - struct ss_request req; - struct gsm_ussd* ussdq = NULL; - struct ussd_request ussd_req; + 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; - memset(&req, 0, sizeof(req)); - memset(&ussd_req, 0, sizeof(ussd_req)); + if (!conn->subscr) + return -EIO; - DEBUGP(DMM, "handle ussd: %s\n", msgb_hexdump(msg)); + memset(&reqhdr, 0, sizeof(reqhdr)); - gh = msgb_l3(msg); - rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req); + 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(DMM, "Unhandled SS\n"); - ussdq = get_by_tid(conn, req.transaction_id); - if (ussdq) { - ussd_sup_send_reject(conn, ussdq->uniq_id, 0); - goto failed_transaction; + DEBUGP(DSS, "Incorrect SS header\n"); + if (!trans) { + goto release_conn; } - goto transaction_not_found; + /* don't know how to process */ + goto failed_transaction; } - switch (req.message_type) { + + switch (reqhdr.message_type) { case GSM0480_MTYPE_REGISTER: - ussdq = ussd_session_alloc(conn, req.transaction_id, USSD_MO); - if (!ussdq) { - DEBUGP(DMM, "Failed to create new session\n"); + 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; } - ussdq->invoke_id = req.invoke_id; + + trans->conn = conn; + trans->ss.invoke_id = invoke_id; + trans->ss.mo = 1; + trans->ss.dirty = 1; break; + case GSM0480_MTYPE_FACILITY: - ussdq = get_by_tid(conn, req.transaction_id); - if (!ussdq) { - ussdq = get_by_iid(conn, req.invoke_id); - if (!ussdq) { - DEBUGP(DMM, "no session found invoke_id=%d tid=%d\n", - req.invoke_id, req.transaction_id); - goto transaction_not_found; + 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: - // FIXME handle parsing in libosmocore - ussdq = get_by_tid(conn, req.transaction_id); - if (!ussdq) { - DEBUGP(DMM, "RELEASE_COMPLETE to non-existing transaction!\n"); + if (!trans) { + DEBUGP(DSS, "RELEASE_COMPLETE to non-existing transaction!\n"); goto release_conn; } - ussd_session_free(ussdq); - ussd_sup_send_reject(conn, ussdq->uniq_id, req.opcode); + trans_free(trans); goto release_conn; } - req.invoke_id = ussdq->uniq_id; - rc = subscr_tx_uss_message(&req, conn->subscr); + rc = ussd_map_tx_message(conn->subscr->group->net, &reqhdr, + conn->subscr->extension, trans->callref, + gh->data + reqhdr.component_offset); if (rc) { - DEBUGP(DMM, "Unable tp send uss over sup reason: %d\n", 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: - ussd_session_free(ussdq); + trans_free(trans); transaction_not_found: - ussd_req.invoke_id = req.invoke_id; - ussd_req.transaction_id = req.transaction_id; - gsm0480_send_ussd_reject(conn, msg, &ussd_req); + gsm0480_send_ussd_reject(conn, invoke_id, transaction_id); release_conn: msc_release_connection(conn); return rc; - -#if 0 - ussdq = get_by_iid(conn, req.invoke_id); - - // TODO FIXME !!!! Replace by message_type - switch (req.opcode) { - case GSM0480_OP_CODE_PROCESS_USS_REQ: - if (ussdq) { - /* new session with the same id as an open session, destroy both */ - DEBUGP(DMM, "Duplicate session? invoke_id: %d\n", req.invoke_id); - goto failed; - } - - if (req.component_type != GSM0480_CTYPE_INVOKE) { - DEBUGP(DMM, "processUSS with component_type 0x%02x\n", req.component_type); - goto failed; - } - - ussdq = ussd_session_alloc(conn); - if (!ussdq) { - DEBUGP(DMM, "Failed to create new session\n"); - goto failed; - } - - ussdq->conn = conn; - ussdq->invoke_id = req.invoke_id; - ussdq->transaction_id = req.transaction_id; - break; - - case GSM0480_OP_CODE_USS_REQUEST: - if (!ussdq) { - DEBUGP(DMM, "no session found for USS_REQUEST with invoke_id=%d\n", req.invoke_id); - goto failed; - } - if (req.component_type != GSM0480_CTYPE_RETURN_RESULT) { - DEBUGP(DMM, "USS with component_type 0x%02x\n", req.component_type); - goto failed; - } - - ussdq->current_transaction_id = req.transaction_id; - break; - - default: - DEBUGP(DMM, "Unhandled opcode: 0x%02x, component_type: 0x%02x, text: %s\n", - req.opcode, req.component_type, req.ussd_text); - goto failed; - } - - // ACHTUNG! FIXME!! FIXME!! Introduce transaction ID instead - // Override Invoke ID - req.invoke_id = ussdq->uniq_id; - rc = subscr_tx_uss_message(&req, conn->subscr); - if (rc) { - DEBUGP(DMM, "Unable tp send uss over sup reason: %d\n", rc); - goto failed; - } - - return 0; -#endif -#if 0 - struct ussd_request req; - struct gsm48_hdr *gh; - - memset(&req, 0, sizeof(req)); - gh = msgb_l3(msg); - rc = gsm0480_decode_ussd_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; - } - - /* Release-Complete */ - if (req.text[0] == '\0') - return 0; - - if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)req.text)) { - DEBUGP(DMM, "USSD: Own number requested\n"); - rc = send_own_number(conn, msg, &req); - } else { - rc = subscr_tx_uss_message(req, conn->subscr); - - - //TODO: - } -#endif -#if 0 -failed: - // TODO handle error on SUP end - if (ussdq) { - ussd_session_free(ussdq); - } - - ussd_req.invoke_id = req.invoke_id; - ussd_req.transaction_id = req.transaction_id; - gsm0480_send_ussd_reject(conn, msg, &ussd_req); - /* check if we can release it */ - msc_release_connection(conn); - return rc; -#endif } -#if 0 - -/* 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 ussd_request *req); - - -/* A network-specific handler function */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req) +void _ussd_trans_free(struct gsm_trans *trans) { - char *own_number = conn->subscr->extension; - char response_string[GSM_EXTENSION_LENGTH + 20]; + if (trans->ss.dirty) { + trans->ss.dirty = 0; - /* 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); + //ussd_sup_send_reject(trans->net, trans->callref, trans->ss.invoke_id, 0); + ussd_sup_send_reject(trans->net, trans->callref); + } } -#endif + diff --git a/openbsc/src/libmsc/vty_interface_layer3.c b/openbsc/src/libmsc/vty_interface_layer3.c index 3b5778eac..9583a5b29 100644 --- a/openbsc/src/libmsc/vty_interface_layer3.c +++ b/openbsc/src/libmsc/vty_interface_layer3.c @@ -50,7 +50,8 @@ #include <openbsc/sms_queue.h> #include <openbsc/mncc_int.h> #include <openbsc/handover.h> -#include <openbsc/gsm_sup.h> +#include <openbsc/gprs_gsup_client.h> +#include <openbsc/gsm_ussd_map.h> #include <osmocom/vty/logging.h> @@ -1035,19 +1036,20 @@ DEFUN(sup_ussd_destination, sup_ussd_destination_cmd, struct gsm_network *gsmnet = gsmnet_from_vty(vty); if (gsmnet->ussd_sup_client) { - LOGP(DSUP, LOGL_FATAL, "Can't create two USSD SUP clients\n"); + LOGP(DSS, LOGL_FATAL, "Can't create two USSD SUP clients\n"); vty_out(vty, "%%USSD SUP client already configured%s", VTY_NEWLINE); return CMD_WARNING; } gsmnet->ussd_sup_client = gprs_gsup_client_create( - argv[0], atoi(argv[1]), &sup_read_cb); + argv[0], atoi(argv[1]), &ussd_map_read_cb); if (!gsmnet->ussd_sup_client) { - LOGP(DSUP, LOGL_FATAL, "Cannot set up USSD SUP socket\n"); + LOGP(DSS, LOGL_FATAL, "Cannot set up USSD SUP socket\n"); vty_out(vty, "%%Cannot set up USSD SUP socket%s", VTY_NEWLINE); return CMD_WARNING; } + gsmnet->ussd_sup_client->data = gsmnet; return CMD_SUCCESS; } diff --git a/openbsc/src/reg-proxy/Makefile.am b/openbsc/src/reg-proxy/Makefile.am index 0bb6c528f..b57af47ff 100644 --- a/openbsc/src/reg-proxy/Makefile.am +++ b/openbsc/src/reg-proxy/Makefile.am @@ -1,12 +1,10 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) AM_CFLAGS=-Wall $(COVERAGE_CFLAGS) \ $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \ - $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) \ - -I/usr/include/sofia-sip-1.12 - + $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) AM_LDFLAGS = $(COVERAGE_LDFLAGS) -bin_PROGRAMS = reg-proxy ussd-proxy +bin_PROGRAMS = reg-proxy reg_proxy_SOURCES = \ ../gprs/gsm_04_08_gprs.c \ @@ -22,10 +20,3 @@ reg_proxy_LDADD = \ $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS) -ussd_proxy_SOURCES = \ - ussd_proxy.c - -ussd_proxy_LDADD = \ - -lsofia-sip-ua \ - $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS) diff --git a/openbsc/src/reg-proxy/sup.c b/openbsc/src/reg-proxy/sup.c index a3f1263f2..4c2f8cbd6 100644 --- a/openbsc/src/reg-proxy/sup.c +++ b/openbsc/src/reg-proxy/sup.c @@ -23,6 +23,7 @@ static int handle_sup_upd_loc_req(struct gsm_sup_server *sup_server, return rc; } +#if 0 static int handle_sup_ss(struct gsm_sup_server *sup_server, struct ss_request *ss, const char* extention) @@ -40,7 +41,6 @@ static int handle_sup_ss(struct gsm_sup_server *sup_server, return rc; } - enum { FMAP_MSISDN = 0x80 }; @@ -223,6 +223,8 @@ static int rx_sup_uss_message(struct gsm_sup_server *sup_server, const uint8_t* #endif } +#endif + int rx_sup_message(struct gsm_sup_server *sup_server, struct msgb *msg) { uint8_t *data = msgb_l2(msg); @@ -231,14 +233,14 @@ int rx_sup_message(struct gsm_sup_server *sup_server, struct msgb *msg) struct gprs_gsup_message sup_msg = {0}; //struct gsm_subscriber *subscr; - +#if 0 if (*data == GPRS_GSUP_MSGT_MAP) { LOGP(DSUP, LOGL_INFO, "Receive USS: %s\n", msgb_hexdump(msg)); return rx_sup_uss_message(sup_server, data, data_len); } - +#endif rc = gprs_gsup_decode(data, data_len, &sup_msg); if (rc < 0) { LOGP(DSUP, LOGL_ERROR, diff --git a/openbsc/src/ussd-proxy/Makefile.am b/openbsc/src/ussd-proxy/Makefile.am new file mode 100644 index 000000000..3f308dd1b --- /dev/null +++ b/openbsc/src/ussd-proxy/Makefile.am @@ -0,0 +1,19 @@ +if BUILD_USSD_PROXY +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(COVERAGE_CFLAGS) \ + $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) \ + -I/usr/include/sofia-sip-1.12 -DNO_GSM0480_SEND_FUNC + +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = ussd-proxy + +ussd_proxy_SOURCES = \ + ussd_proxy.c ../libmsc/gsm_ussd_map_proto.c ../libmsc/gsm_04_80.c + +ussd_proxy_LDADD = \ + -lsofia-sip-ua \ + $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS) +endif diff --git a/openbsc/src/reg-proxy/ussd_proxy.c b/openbsc/src/ussd-proxy/ussd_proxy.c index 5488f8055..572989b10 100644 --- a/openbsc/src/reg-proxy/ussd_proxy.c +++ b/openbsc/src/ussd-proxy/ussd_proxy.c @@ -8,6 +8,7 @@ #include <stdio.h> #include <assert.h> #include <unistd.h> +#include <ctype.h> typedef struct context_s context_t; #define NTA_OUTGOING_MAGIC_T context_t @@ -38,122 +39,12 @@ typedef struct operation operation_t; #include <osmocom/core/linuxlist.h> #include <openbsc/gprs_gsup_messages.h> +#include <openbsc/gsm_ussd_map_proto.h> +#include <openbsc/gsm_04_80.h> #include <iconv.h> -typedef uint8_t sup_tcap_tid_t; - -/******************************************************************************/ -/* Put this into separate file */ - -enum { - FMAP_MSISDN = 0x80 -}; -static int rx_uss_message_parse(struct ss_request *ss, - const uint8_t* data, - size_t len, - char* extention, - size_t extention_len) -{ - const uint8_t* const_data = data; - - if (len < 1 + 2 + 3 + 3) - return -1; - - /* skip GPRS_GSUP_MSGT_MAP */ - ss->message_type = *(++const_data); - ss->component_type = *(++const_data); - 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(DLCTRL, "Unknown code: %d\n", *const_data); - return -1; - } - } - - return 0; -} - -static int subscr_uss_message(struct msgb *msg, - struct ss_request *req, - const char* extention) -{ - size_t bcd_len = 0; - uint8_t *gsup_indicator; - - gsup_indicator = msgb_put(msg, 4); - - /* First byte should always be GPRS_GSUP_MSGT_MAP */ - gsup_indicator[0] = GPRS_GSUP_MSGT_MAP; - gsup_indicator[1] = req->message_type; - /* TODO ADD tid */ - gsup_indicator[2] = 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 (extention) { - uint8_t bcd_buf[32]; - bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), 0, - extention); - msgb_tlv_put(msg, FMAP_MSISDN, bcd_len - 1, &bcd_buf[1]); - } - - /* fill actual length */ - gsup_indicator[3] = 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; -} - -/******************************************************************************/ +typedef uint32_t sup_tcap_tid_t; typedef struct isup_connection isup_connection_t; @@ -170,15 +61,22 @@ struct isup_connection { struct msgb *pending_msg; }; +typedef enum ss_type { + TYPE_USSD, + TYPE_SS_OTHER +} ss_type_t; + struct ussd_session { isup_connection_t *conn; - - // int32_t transaction_id; + sup_tcap_tid_t ref; int ms_originated; - struct ss_request rigester_msg; - char extention[32]; + + ss_type_t type; + + uint8_t ss_code; + struct ss_request rigester_msg; }; struct context_s { @@ -203,9 +101,6 @@ struct context_s { iconv_t* utf8_to_latin1; iconv_t* latin1_to_utf8; - int dont_encode_in_latin1; - int force_7bit; - /* Array of isup connections */ struct isup_connection isup[1]; @@ -233,10 +128,14 @@ static int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len, const char* msg, unsigned msg_len); static -int ussd_send_data_ss(isup_connection_t *conn, struct ss_request* reply); +int ussd_send_data_ss(isup_connection_t *conn, + uint8_t message_type, + const uint8_t *component, + uint8_t component_len, + uint32_t ref); static -int ussd_send_reject(isup_connection_t *conn, uint8_t invoke_id, uint8_t opcode); +int ussd_send_reject(isup_connection_t *conn, uint32_t ref, uint8_t invoke_id); static const char* get_unknown_header(sip_t const *sip, const char *header) { @@ -328,11 +227,11 @@ static int ussd_parse_xml(const char *xml, } // Operation APIs -static operation_t* operation_find_by_tid(context_t* ctx, sup_tcap_tid_t id) +static operation_t* operation_find_by_tid(context_t* ctx, sup_tcap_tid_t ref) { operation_t* op; llist_for_each_entry(op, &ctx->operation_list, list) { - if (op->ussd.rigester_msg.invoke_id == id) + if (op->ussd.ref == ref) return op; } return NULL; @@ -372,11 +271,18 @@ static void operation_destroy(operation_t* op) llist_del(&op->list); op->ctx->operation_count--; - fprintf(stderr, "--- operation %*.s from %s destroyed (sessions: %d)\n", - op->ussd.rigester_msg.ussd_text_len, - op->ussd.rigester_msg.ussd_text, - op->ussd.extention, - op->ctx->operation_count); + if (op->ussd.type == TYPE_USSD) { + fprintf(stderr, "--- operation %*.s from %s destroyed (sessions: %d)\n", + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text, + op->ussd.extention, + op->ctx->operation_count); + } else { + fprintf(stderr, "--- operation 0x%02x from %s destroyed (sessions: %d)\n", + op->ussd.ss_code, + op->ussd.extention, + op->ctx->operation_count); + } /* release operation context information */ su_free(op->ctx->home, op); @@ -394,12 +300,21 @@ void proxy_r_invite(int status, fprintf(stderr, "*** Got reply %d for INVITE\n", status); if (status == 200) { nua_ack(nh, TAG_END()); - } else { - printf("response to INVITE: %03d %s\n", status, phrase); + } else if (hmagic->ussd.type == TYPE_USSD) { + printf("response to USSD INVITE: %03d %s\n", status, phrase); ussd_send_reject(hmagic->ussd.conn, - hmagic->ussd.rigester_msg.invoke_id, - hmagic->ussd.rigester_msg.opcode); + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id); + operation_destroy(hmagic); + } else { + printf("response to SS INVITE: %03d %s\n", status, phrase); + + ussd_send_data_ss(hmagic->ussd.conn, + GSM0480_MTYPE_RELEASE_COMPLETE, + NULL, + 0, + hmagic->ussd.ref); operation_destroy(hmagic); } } @@ -452,11 +367,69 @@ void proxy_i_bye(int status, fprintf(stderr, "*** response BYE with %d satus is malformed, drop session\n", status); ussd_send_reject(hmagic->ussd.conn, - hmagic->ussd.rigester_msg.invoke_id, - hmagic->ussd.rigester_msg.opcode); + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id); operation_destroy(hmagic); } +static +uint8_t get_nibble(uint8_t a) +{ + if (a >= '0' && a <= '9') + return a-'0'; + else if (a >= 'A' && a <= 'F') + return a-'A'; + else if (a >= 'a' && a <= 'f') + return a-'a'; + + fprintf(stderr, "*** Incorrect nibble deteced: %02x\n", a); + return 0; +} + +void proxy_i_bye_ss(int status, + char const *phrase, + nua_t *nua, + nua_magic_t *magic, + nua_handle_t *nh, + nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + const char* pl_txt = sip->sip_payload->pl_data; + unsigned pl_txt_len = sip->sip_payload->pl_len; + uint8_t buffer[256]; + uint8_t buflen = 0; + int i; + + for (i = 0; i < pl_txt_len && buflen < sizeof(buflen) - 1; ) { + uint8_t hi_nibble = pl_txt[i++]; + if (hi_nibble == 0) + break; + if (isspace(hi_nibble)) + continue; + + uint8_t lo_nibble = pl_txt[i++]; + if (lo_nibble == 0) + break; + + buffer[buflen++] = (get_nibble(hi_nibble) << 4) | + get_nibble(lo_nibble); + } + + if (buflen > 1) { + if (buffer[1] != buflen - 2) { + fprintf(stderr, "*** parsed %d len, but should be %d (%s)", + buflen, buffer[1] + 2, pl_txt); + } + } + + ussd_send_data_ss(hmagic->ussd.conn, + GSM0480_MTYPE_RELEASE_COMPLETE, + buffer, + buflen, + hmagic->ussd.ref); +} + void proxy_r_bye(int status, char const *phrase, nua_t *nua, @@ -552,13 +525,33 @@ void proxy_info(int status, response ? "response" : "request", status); ussd_send_reject(hmagic->ussd.conn, - hmagic->ussd.rigester_msg.invoke_id, - hmagic->ussd.rigester_msg.opcode); + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id); operation_destroy(hmagic); } -int ussd_create_xml_ascii(char *content, size_t max_len, const char* language, const char* msg, int msg_len) +int ussd_create_xml_latin1(context_t* ctx, + char *content, size_t max_len, + const char* inbuf_latin1, int buf_len) { + const char *language = "en"; + char tmpbuf_utf8[2*MAX_LEN_USSD_STRING]; + unsigned tmpbuf_utf8_len; + + char* inbuf = (char*)inbuf_latin1; + size_t inleft = buf_len; + char* outbuf = tmpbuf_utf8; + size_t outleft = sizeof(tmpbuf_utf8); + size_t s; + + s = iconv(ctx->latin1_to_utf8, &inbuf, &inleft, &outbuf, &outleft); + if (s == (size_t)-1) { + LOGP(DLCTRL, LOGL_ERROR, "Unable to encode latin1 into utf8\n"); + return 0; + } + + tmpbuf_utf8_len = outbuf - tmpbuf_utf8; + int content_len = snprintf(content, max_len, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<ussd-data>\n" @@ -566,7 +559,7 @@ int ussd_create_xml_ascii(char *content, size_t max_len, const char* language, c "<ussd-string>%.*s</ussd-string>\n" "</ussd-data>", language, - msg_len, msg); + tmpbuf_utf8_len, tmpbuf_utf8); if (content_len > max_len) { content[max_len - 1] = 0; return 0; @@ -574,15 +567,118 @@ int ussd_create_xml_ascii(char *content, size_t max_len, const char* language, c return 1; } +static int decode_to_latin1(char* outbuf, unsigned size, + const uint8_t* msg, unsigned msg_len, uint8_t lang) +{ + if (lang == 0x0f) { + return gsm_7bit_decode_n_ussd(outbuf, size, msg, msg_len); + } else { + LOGP(DLCTRL, LOGL_ERROR, "Unknown language: 0x%02x\n", lang); + return 0; + } +} + /* URL_RESERVED_CHARS in sofia is not strict enough as in RFC3986 */ #define RFC3986_RESERVED_CHARS "!*'();:@&=+$,/?#[]" +int ss_session_open_mo(operation_t *op, + isup_connection_t *conn, + const uint8_t* component, + uint8_t component_len, + uint32_t ref, + const char* extention) +{ + char buffer[512+1]; + int i; + context_t* ctx = op->ctx; + sip_to_t *to = NULL; + sip_to_t *from = NULL; + url_t to_url, from_url; + char* to_url_str; + char* from_url_str; + + op->ussd.ref = ref; + op->ussd.conn = conn; + op->ussd.ms_originated = 1; + op->ussd.type = TYPE_SS_OTHER; + + strncpy(op->ussd.extention, extention, sizeof(op->ussd.extention)); + + for (i = 0; i < component_len; ++i) { + uint8_t nibble_h = component[i] >> 4; + uint8_t nibble_l = component[i] & 0xf; + + buffer[2*i ] = (nibble_h < 10) ? '0' + nibble_h : 'a' + nibble_h - 10; + buffer[2*i + 1] = (nibble_l < 10) ? '0' + nibble_l : 'a' + nibble_l - 10; + } + buffer[2*i] = 0; + + /* Destination address */ + to_url = *ctx->to_url; + to_url.url_user = "mapss"; + to_url_str = url_as_string(ctx->home, &to_url); + if (to_url_str == NULL) { + goto failed_create_handle; + } + to = sip_to_create(ctx->home, (url_string_t *)to_url_str); + su_free(ctx->home, to_url_str); + if (!to) { + goto failed_create_handle; + } + + /* Source address */ + from_url = *ctx->self_url; + from_url.url_user = extention; + from_url_str = url_as_string(ctx->home, &from_url); + if (from_url_str == NULL) { + goto failed_create_handle; + } + from = sip_from_create(ctx->home, (url_string_t *)from_url_str); + su_free(ctx->home, from_url_str); + if (!to) { + goto failed_create_handle; + } + + /* create operation handle */ + op->handle = nua_handle(ctx->nua, + op, + SIPTAG_TO(to), + SIPTAG_FROM(from), + NUTAG_M_USERNAME(extention), + TAG_END()); + + su_free(ctx->home, from); + su_free(ctx->home, to); + from = NULL; + to = NULL; + + if (op->handle == NULL) { + goto failed_create_handle; + } + + nua_invite(op->handle, + SIPTAG_CONTENT_TYPE_STR("application/map-ss-binary"), + SIPTAG_PAYLOAD_STR(buffer), + TAG_END()); + return 0; + +failed_create_handle: + if (from != NULL) + su_free(ctx->home, from); + if (to != NULL) + su_free(ctx->home, to); + + return -1; +} + int ussd_session_open_mo(operation_t *op, isup_connection_t *conn, struct ss_request* ss, + uint32_t ref, const char* extention) { char content[1024]; + char decoded[MAX_LEN_USSD_STRING + 1]; char escaped_to[512]; context_t* ctx = op->ctx; sip_to_t *to = NULL; @@ -591,23 +687,32 @@ int ussd_session_open_mo(operation_t *op, char* to_url_str; char* from_url_str; + int decoded_len; + + op->ussd.ref = ref; op->ussd.conn = conn; op->ussd.ms_originated = 1; + op->ussd.type = TYPE_USSD; op->ussd.rigester_msg = *ss; strncpy(op->ussd.extention, extention, sizeof(op->ussd.extention)); - /* TODO add language support !!! */ + decoded_len = decode_to_latin1(decoded, MAX_LEN_USSD_STRING, + op->ussd.rigester_msg.ussd_text, + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text_language); + if (decoded_len <= 0) { + goto failed_to_parse_xml; + } + decoded[decoded_len] = 0; - if (!ussd_create_xml_ascii(content, sizeof(content), - "en", - (const char* )op->ussd.rigester_msg.ussd_text, - op->ussd.rigester_msg.ussd_text_len)) { + if (!ussd_create_xml_latin1(ctx, content, sizeof(content), + decoded, decoded_len)) { goto failed_to_parse_xml; } /* Destination address */ - url_escape(escaped_to, (const char*)ss->ussd_text, RFC3986_RESERVED_CHARS); + url_escape(escaped_to, decoded, RFC3986_RESERVED_CHARS); to_url = *ctx->to_url; to_url.url_user = escaped_to; to_url_str = url_as_string(ctx->home, &to_url); @@ -653,6 +758,7 @@ int ussd_session_open_mo(operation_t *op, } nua_invite(op->handle, + SIPTAG_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"), SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"), SIPTAG_PAYLOAD_STR(content), TAG_END()); @@ -673,10 +779,20 @@ int ussd_session_facility(operation_t *op, const char* extention) { char content[1024]; - if (!ussd_create_xml_ascii(content, sizeof(content), - "en", - (const char* )ss->ussd_text, - ss->ussd_text_len)) { + char decoded[MAX_LEN_USSD_STRING + 1]; + int decoded_len; + + decoded_len = decode_to_latin1(decoded, MAX_LEN_USSD_STRING, + op->ussd.rigester_msg.ussd_text, + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text_language); + if (decoded_len <= 0) { + return -1; + } + decoded[decoded_len] = 0; + + if (!ussd_create_xml_latin1(op->ctx, content, sizeof(content), + decoded, decoded_len)) { return -1; } @@ -708,15 +824,20 @@ void context_callback(nua_event_t event, break; case nua_i_info: - proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 0); + if (hmagic->ussd.type == TYPE_USSD) + proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 0); break; case nua_r_info: - proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 1); + if (hmagic->ussd.type == TYPE_USSD) + proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 1); break; case nua_i_bye: - proxy_i_bye(status, phrase, nua, magic, nh, hmagic, sip, tags); + if (hmagic->ussd.type == TYPE_USSD) + proxy_i_bye(status, phrase, nua, magic, nh, hmagic, sip, tags); + else + proxy_i_bye_ss(status, phrase, nua, magic, nh, hmagic, sip, tags); break; case nua_i_invite: @@ -749,28 +870,42 @@ void context_callback(nua_event_t event, static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, size_t len) { char extention[32] = {0}; - struct ss_request ss; + struct ss_header ss; + struct ss_request ssreq; + uint32_t ref; operation_t* op; int rc; context_t *ctx = sup_conn->ctx; memset(&ss, 0, sizeof(ss)); - if (rx_uss_message_parse(&ss, data, len, extention, sizeof(extention))) { + if (rx_uss_message_parse(data, len, &ss, &ref, extention, sizeof(extention))) { LOGP(DLCTRL, LOGL_ERROR, "Can't parse uss message\n"); - goto err_send_reject; + goto err_bad_packet; + } + + memset(&ssreq, 0, sizeof(ssreq)); + rc = gsm0480_parse_ss_facility(data + ss.component_offset, + ss.component_length, + &ssreq); + if (!rc) { + LOGP(DLCTRL, LOGL_ERROR, "Can't parse facility message\n"); + goto err_bad_component; } - LOGP(DLCTRL, LOGL_ERROR, "Got mtype=0x%02x invoke_id=0x%02x opcode=0x%02x component_type=0x%02x text=%s\n", - ss.message_type, ss.invoke_id, ss.opcode, ss.component_type, ss.ussd_text); + LOGP(DLCTRL, LOGL_ERROR, "Got ref=%d mtype=0x%02x invoke_id=0x%02x opcode=0x%02x ss_code=0x%02x component_type=0x%02x text=%s\n", ref, + ss.message_type, ssreq.invoke_id, ssreq.opcode, ssreq.ss_code, ssreq.component_type, ssreq.ussd_text); switch (ss.message_type) { case GSM0480_MTYPE_REGISTER: - if (ss.component_type != GSM0480_CTYPE_INVOKE) { - LOGP(DLCTRL, LOGL_ERROR, "Non-INVOKE component type in REGISTER: 0x%02x\n", ss.component_type); + if (ssreq.component_type != GSM0480_CTYPE_INVOKE) { + LOGP(DLCTRL, LOGL_ERROR, "Non-INVOKE component type in REGISTER: 0x%02x\n", ssreq.component_type); goto err_send_reject; } - if (ss.opcode != GSM0480_OP_CODE_PROCESS_USS_REQ) { - LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ss.opcode); + if (ssreq.opcode == GSM0480_OP_CODE_PROCESS_USS_DATA || + ssreq.opcode == GSM0480_OP_CODE_USS_NOTIFY || + ssreq.opcode == GSM0480_OP_CODE_USS_REQUEST) { + + LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ssreq.opcode); goto err_send_reject; } /* Create new session */ @@ -779,46 +914,68 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, LOGP(DLCTRL, LOGL_ERROR, "Unable to allocate new session\n"); goto err_send_reject; } - LOGP(DLCTRL, LOGL_ERROR, "New session %.*s from %s, active: %d\n", - ss.ussd_text_len, - ss.ussd_text, - extention, - ctx->operation_count); - rc = ussd_session_open_mo(op, sup_conn, &ss, extention); - if (rc < 0) { - operation_destroy(op); - goto err_send_reject; + if (ssreq.opcode == GSM0480_OP_CODE_PROCESS_USS_REQ) { + LOGP(DLCTRL, LOGL_ERROR, "New session %.*s from %s, active: %d\n", + ssreq.ussd_text_len, + ssreq.ussd_text, + extention, + ctx->operation_count); + + op->ussd.ss_code = 0; + rc = ussd_session_open_mo(op, sup_conn, &ssreq, ref, extention); + if (rc < 0) { + operation_destroy(op); + goto err_send_reject; + } + } else { + LOGP(DLCTRL, LOGL_ERROR, "New session SS 0x%02x from %s, active: %d\n", + ssreq.opcode, + extention, + ctx->operation_count); + + op->ussd.ss_code = ssreq.ss_code; + op->ussd.rigester_msg = ssreq; + rc = ss_session_open_mo(op, + sup_conn, + data + ss.component_offset, + ss.component_length, + ref, + extention); + if (rc < 0) { + operation_destroy(op); + goto err_send_reject; + } } break; case GSM0480_MTYPE_FACILITY: //Only MS-originated Menu session is supported, so we ignore INVOKE here - if (ss.component_type != GSM0480_CTYPE_RETURN_RESULT && - ss.component_type != GSM0480_CTYPE_RETURN_ERROR && - ss.component_type != GSM0480_CTYPE_REJECT) { - LOGP(DLCTRL, LOGL_ERROR, "Non-{RESULT/RETURN_ERROR/REJECT} component type in FACILITY: 0x%02x\n", ss.component_type); + if (ssreq.component_type != GSM0480_CTYPE_RETURN_RESULT && + ssreq.component_type != GSM0480_CTYPE_RETURN_ERROR && + ssreq.component_type != GSM0480_CTYPE_REJECT) { + LOGP(DLCTRL, LOGL_ERROR, "Non-{RESULT/RETURN_ERROR/REJECT} component type in FACILITY: 0x%02x\n", ssreq.component_type); goto err_send_reject; } // ///////////////////////////////////////////////// // TODO handle RETURN_ERROR/REJECT - if (ss.component_type != GSM0480_CTYPE_RETURN_RESULT) { - LOGP(DLCTRL, LOGL_ERROR, "Component type in FACILITY: 0x%02x is not implemented yet\n", ss.component_type); + if (ssreq.component_type != GSM0480_CTYPE_RETURN_RESULT) { + LOGP(DLCTRL, LOGL_ERROR, "Component type in FACILITY: 0x%02x is not implemented yet\n", ssreq.component_type); goto err_send_reject; } - if (ss.opcode != GSM0480_OP_CODE_USS_REQUEST) { - LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ss.opcode); + if (ssreq.opcode != GSM0480_OP_CODE_USS_REQUEST) { + LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ssreq.opcode); goto err_send_reject; } - op = operation_find_by_tid(ctx, ss.invoke_id); + op = operation_find_by_tid(ctx, ref); if (op == NULL) { LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found\n", - ss.invoke_id); + ssreq.invoke_id); goto err_send_reject; } // TODO check result!! MO/MT error handling - rc = ussd_session_facility(op, &ss, extention); + rc = ussd_session_facility(op, &ssreq, extention); if (rc < 0) { operation_destroy(op); goto err_send_reject; @@ -826,17 +983,14 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, break; case GSM0480_MTYPE_RELEASE_COMPLETE: - op = operation_find_by_tid(ctx, ss.invoke_id); + op = operation_find_by_tid(ctx, ref); if (op == NULL) { LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found for RELEASE_COMPLETE\n", - ss.invoke_id); + ssreq.invoke_id); return 0; } - // NOTE: Add ContentType for 3rd party software workaround, it's not needed by standard - nua_bye(op->handle, - SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"), - TAG_END()); + nua_bye(op->handle, TAG_END()); break; default: @@ -847,29 +1001,63 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, return 0; err_send_reject: - ussd_send_reject(sup_conn, ss.invoke_id, ss.opcode); + ussd_send_reject(sup_conn, ref, ssreq.invoke_id); + return -1; + +err_bad_component: + return ussd_send_data_ss(sup_conn, + GSM0480_MTYPE_RELEASE_COMPLETE, + NULL, + 0, + ref); + return -1; + +err_bad_packet: + // Disconnect ? return -1; } -int ussd_send_reject(isup_connection_t *conn, uint8_t invoke_id, uint8_t opcode) +int ussd_send_reject(isup_connection_t *conn, uint32_t ref, uint8_t invoke_id) { - struct ss_request error_ss; + uint8_t buffer[2+3+3]; + + buffer[0] = GSM0480_CTYPE_REJECT; + buffer[1] = 3+3; - memset(&error_ss, 0, sizeof(error_ss)); - error_ss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; - error_ss.component_type = GSM0480_CTYPE_REJECT; - error_ss.invoke_id = invoke_id; - error_ss.opcode = opcode; + buffer[2] = GSM0480_COMPIDTAG_INVOKE_ID; + buffer[3] = 1; + buffer[4] = invoke_id; - return ussd_send_data_ss(conn, &error_ss); + buffer[5] = GSM_0480_PROBLEM_CODE_TAG_GENERAL; + buffer[6] = 1; + buffer[7] = GSM_0480_GEN_PROB_CODE_UNRECOGNISED; + + return ussd_send_data_ss(conn, + GSM0480_MTYPE_RELEASE_COMPLETE, + buffer, + sizeof(buffer), + ref); } -int ussd_send_data_ss(isup_connection_t *conn, struct ss_request* reply) +int ussd_send_data_ss(isup_connection_t *conn, + uint8_t message_type, + const uint8_t* component, + uint8_t component_len, + uint32_t ref) { struct msgb *outmsg = msgb_alloc_headroom(4000, 64, __func__); + struct ss_header hdr; + + hdr.transaction_id = 0; + hdr.message_type = message_type; + hdr.component_length = component_len; + hdr.component_offset = 0; + subscr_uss_message(outmsg, - reply, - NULL); + &hdr, + NULL, + ref, + component); LOGP(DLCTRL, LOGL_ERROR, "Sending USS, will send: %s\n", msgb_hexdump(outmsg)); @@ -877,33 +1065,27 @@ int ussd_send_data_ss(isup_connection_t *conn, struct ss_request* reply) return sup_server_send(conn, outmsg); } -static int is_string_ascii(const char* msg, unsigned msg_len) -{ - unsigned i; - for (i = 0; i < msg_len; ++i) { - if (*((uint8_t*)(msg++)) >= 0x80) - return 0; - } - return 1; -} - int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len, const char* msg, unsigned msg_len) { + struct msgb *buf; struct ss_request ss; + int rc; + uint8_t message_type; + memset(&ss, 0, sizeof(ss)); // TODO handle language if (msg == NULL) { - ss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + message_type = GSM0480_MTYPE_RELEASE_COMPLETE; ss.component_type = GSM0480_CTYPE_REJECT; ss.opcode = op->ussd.rigester_msg.opcode; } else if (last) { - ss.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + message_type = GSM0480_MTYPE_RELEASE_COMPLETE; ss.component_type = GSM0480_CTYPE_RETURN_RESULT; ss.opcode = op->ussd.rigester_msg.opcode; } else { - ss.message_type = GSM0480_MTYPE_FACILITY; + message_type = GSM0480_MTYPE_FACILITY; ss.component_type = (op->ussd.ms_originated) ? GSM0480_CTYPE_INVOKE : GSM0480_CTYPE_RETURN_RESULT; ss.opcode = GSM0480_OP_CODE_USS_REQUEST; @@ -912,57 +1094,57 @@ int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_le ss.invoke_id = op->ussd.rigester_msg.invoke_id; if (msg) { - if (msg_len > MAX_LEN_USSD_STRING) { - msg_len = MAX_LEN_USSD_STRING; - } - if (is_string_ascii(msg, msg_len)) { - // Only ASCII characters, no need extra convertion to - // GSM 7-bit, coding will be done on the other end of SUP - ss.ussd_text_len = msg_len; - ss.ussd_text_language = 0x80; - strncpy((char*)ss.ussd_text, msg, msg_len); - } else { - char* inbuf = (char*)msg; - size_t inleft = msg_len; - char* outbuf = (char*)ss.ussd_text; - size_t outleft = MAX_LEN_USSD_STRING; - size_t s; - // First of all try latin1 - if (!op->ctx->force_7bit && op->ctx->dont_encode_in_latin1) { - s =(size_t)-1; - } else { - s = iconv(op->ctx->utf8_to_latin1, - &inbuf, &inleft, - &outbuf, &outleft); - } + char tmpbuf[MAX_LEN_USSD_STRING + 1]; + + char* inbuf = (char*)msg; + size_t inleft = msg_len; + char* outbuf = (char*)tmpbuf; + size_t outleft = sizeof(tmpbuf); + size_t s; + + // First of all try latin1 + s = iconv(op->ctx->utf8_to_latin1, + &inbuf, &inleft, + &outbuf, &outleft); + if (s == (size_t)-1) { + outbuf = (char*)ss.ussd_text; + outleft = MAX_LEN_USSD_STRING; + + s = iconv(op->ctx->utf8_to_ucs2, + &inbuf, &inleft, + &outbuf, &outleft); if (s == (size_t)-1) { - s = iconv(op->ctx->utf8_to_ucs2, - &inbuf, &inleft, - &outbuf, &outleft); - if (s == (size_t)-1) { - perror("can't convert string from utf8"); - } - // UCS-2 encoding - ss.ussd_text_language = 0x48; - } else { - if (op->ctx->force_7bit) { - // Decode in 7-bit on SUP side - ss.ussd_text_language = 0x80; - } else { - // 8-bit DATA encoding - ss.ussd_text_language = 0x44; - } + perror("can't convert string from utf8"); } + // UCS-2 encoding + ss.ussd_text_language = 0x48; ss.ussd_text_len = (uint8_t*)outbuf - ss.ussd_text; + } else { + int outlen; + + // Set null-termination + outbuf[0] = 0; + gsm_7bit_encode_n_ussd(ss.ussd_text, + MAX_LEN_USSD_STRING, outbuf, &outlen); + ss.ussd_text_len = outlen; + ss.ussd_text_language = 0x0f; } } else { ss.ussd_text_len = 0; - ss.ussd_text_language = 0x80; + ss.ussd_text_language = 0x0f; ss.ussd_text[0] = 0; } - return ussd_send_data_ss(op->ussd.conn, &ss); + buf = gsm0480_compose_ussd_component(&ss); + if (!buf) { + return -1; + } + rc = ussd_send_data_ss(op->ussd.conn, message_type, + buf->data, msgb_length(buf), op->ussd.ref); + msgb_free(buf); + + return rc; } static void timer_function(su_root_magic_t *magic, @@ -976,17 +1158,31 @@ static void timer_function(su_root_magic_t *magic, llist_for_each_entry_safe(op, tmp, &cli->operation_list, list) { su_duration_t lasts = su_duration(n, op->tm_initiated); if (lasts > cli->max_ussd_ses_duration) { - fprintf(stderr, "!!! session %.*s from %s lasted %ld ms, more than thresold %ld ms, destroying\n", - op->ussd.rigester_msg.ussd_text_len, - op->ussd.rigester_msg.ussd_text, - op->ussd.extention, - lasts, - cli->max_ussd_ses_duration); - - - ussd_send_reject(op->ussd.conn, - op->ussd.rigester_msg.invoke_id, - op->ussd.rigester_msg.opcode); + if (op->ussd.type == TYPE_USSD) { + fprintf(stderr, "!!! session %.*s from %s lasted %ld ms, more than thresold %ld ms, destroying\n", + op->ussd.rigester_msg.ussd_text_len, + op->ussd.rigester_msg.ussd_text, + op->ussd.extention, + lasts, + cli->max_ussd_ses_duration); + + + ussd_send_reject(op->ussd.conn, + op->ussd.ref, + op->ussd.rigester_msg.invoke_id); + } else { + fprintf(stderr, "!!! session 0x%02x from %s lasted %ld ms, more than thresold %ld ms, destroying\n", + op->ussd.ss_code, + op->ussd.extention, + lasts, + cli->max_ussd_ses_duration); + + ussd_send_data_ss(op->ussd.conn, + GSM0480_MTYPE_RELEASE_COMPLETE, + NULL, + 0, + op->ussd.ref); + } operation_destroy(op); } } @@ -1037,7 +1233,7 @@ static int isup_handle_connection(context_t *cli, su_wait_t *w, void *p) goto err; case IPAC_PROTO_OSMO: // TODO callback - if (msg->l2h[1] == GPRS_GSUP_MSGT_MAP) { + if (msg->l2h[1] == GPRS_GSUP_MSGT_USSD_MAP) { LOGP(DLCTRL, LOGL_ERROR, "Receive USS: %s\n", msgb_hexdump(msg)); @@ -1154,8 +1350,6 @@ static void Usage(char* progname) " -o <sessions> Maximum number of concurrent USSD sessions\n" " (default: 200)\n" " -l <0-9> sip sofia loglevel, 0 - none; 9 - max\n" - " -L Do not try to encode in 8-bit (use 7-bit or UCS-2)\n" - " -7 Encode Latin1 in GSM 7-bit not in 8-bit (don't mix with -L)\n" , progname); } @@ -1173,8 +1367,6 @@ int main(int argc, char *argv[]) int max_ussd_ses_secs = 90; int max_op_limit = 200; int sip_loglevel = 1; - int dont_try_latin1 = 0; - int force_7bit = 0; int c; while ((c = getopt (argc, argv, "x:p:t:u:D:To:l:L7?")) != -1) { @@ -1205,10 +1397,10 @@ int main(int argc, char *argv[]) sip_loglevel = atoi(optarg); break; case 'L': - dont_try_latin1 = 1; + fprintf(stderr, " -L is now obsolete, ignored\n"); break; case '7': - force_7bit = 1; + fprintf(stderr, " -7 is now obsolete, ignored\n"); break; case '?': default: @@ -1234,8 +1426,6 @@ int main(int argc, char *argv[]) return 1; } - context->dont_encode_in_latin1 = dont_try_latin1; - context->force_7bit = force_7bit; context->utf8_to_latin1=iconv_open("iso8859-1", "utf-8"); context->latin1_to_utf8=iconv_open("utf-8", "iso8859-1"); context->utf8_to_ucs2=iconv_open("utf-16be", "utf-8"); diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am index 9cbc1c172..633bb8638 100644 --- a/openbsc/tests/Makefile.am +++ b/openbsc/tests/Makefile.am @@ -10,6 +10,7 @@ SUBDIRS = \ subscr \ mm_auth \ nanobts_omlattr \ + ussd \ $(NULL) if BUILD_NAT |