--- openbsc/include/openbsc/debug.h | 1 + openbsc/src/libcommon/debug.c | 6 ++++++ 2 files changed, 7 insertions(+)
diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h index 189ca47..6048a5c 100644 --- a/openbsc/include/openbsc/debug.h +++ b/openbsc/include/openbsc/debug.h @@ -34,6 +34,7 @@ enum { DSMPP, DFILTER, DGTPHUB, + DSS, Debug_LastEntry, };
diff --git a/openbsc/src/libcommon/debug.c b/openbsc/src/libcommon/debug.c index 7fb3ecb..6536944 100644 --- a/openbsc/src/libcommon/debug.c +++ b/openbsc/src/libcommon/debug.c @@ -165,6 +165,12 @@ static const struct log_info_cat default_categories[] = { .description = "BSC/NAT IMSI based filtering", .enabled = 1, .loglevel = LOGL_DEBUG, }, + [DSS] = { + .name = "DSS", + .description = "Layer3 Supplementary Service (SS)", + .color = "\033[0;33m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, };
enum log_filter {
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
--- openbsc/include/openbsc/gprs_gsup_messages.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/openbsc/include/openbsc/gprs_gsup_messages.h b/openbsc/include/openbsc/gprs_gsup_messages.h index 8cbc809..fb9c66e 100644 --- a/openbsc/include/openbsc/gprs_gsup_messages.h +++ b/openbsc/include/openbsc/gprs_gsup_messages.h @@ -53,6 +53,8 @@ enum gprs_gsup_iei { };
enum gprs_gsup_message_type { + GPRS_GSUP_MSGT_USSD_MAP = 0b01111111, + GPRS_GSUP_MSGT_UPDATE_LOCATION_REQUEST = 0b00000100, GPRS_GSUP_MSGT_UPDATE_LOCATION_ERROR = 0b00000101, GPRS_GSUP_MSGT_UPDATE_LOCATION_RESULT = 0b00000110,
Just minor remarks...
IMHO it would be good to mention in the commit log the file/function in which the constant will be used, if not adding the constant along with its first use.
Why are you adding the GPRS_GSUP_MSGT_USSD_MAP out of sequence? So far the constants are sorted by bit value, rather comply to that.
~Neels
On Fri, Apr 22, 2016 at 02:41:31PM +0200, Sergey Kostanbaev wrote:
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
openbsc/include/openbsc/gprs_gsup_messages.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/openbsc/include/openbsc/gprs_gsup_messages.h b/openbsc/include/openbsc/gprs_gsup_messages.h index 8cbc809..fb9c66e 100644 --- a/openbsc/include/openbsc/gprs_gsup_messages.h +++ b/openbsc/include/openbsc/gprs_gsup_messages.h @@ -53,6 +53,8 @@ enum gprs_gsup_iei { };
enum gprs_gsup_message_type {
- GPRS_GSUP_MSGT_USSD_MAP = 0b01111111,
- GPRS_GSUP_MSGT_UPDATE_LOCATION_REQUEST = 0b00000100, GPRS_GSUP_MSGT_UPDATE_LOCATION_ERROR = 0b00000101, GPRS_GSUP_MSGT_UPDATE_LOCATION_RESULT = 0b00000110,
-- 1.9.1
--- openbsc/include/openbsc/gsm_04_80.h | 15 +++-- openbsc/src/libmsc/gsm_04_80.c | 128 ++++++++++++++++++++++++------------ openbsc/src/libmsc/ussd.c | 79 ++++++++++++++++++---- 3 files changed, 162 insertions(+), 60 deletions(-)
diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h index 0a60652..371bc17 100644 --- a/openbsc/include/openbsc/gsm_04_80.h +++ b/openbsc/include/openbsc/gsm_04_80.h @@ -7,12 +7,17 @@
struct gsm_subscriber_connection;
-int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, const char* response_text, - const struct ussd_request *req); +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 gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text); int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn); diff --git a/openbsc/src/libmsc/gsm_04_80.c b/openbsc/src/libmsc/gsm_04_80.c index f1d75f2..e6e4a92 100644 --- a/openbsc/src/libmsc/gsm_04_80.c +++ b/openbsc/src/libmsc/gsm_04_80.c @@ -39,6 +39,22 @@ #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; + uint8_t *data = msgb_push(msgb, (origlen > 0x7f) ? 3 : 2); + data[0] = tag; + if (origlen > 0x7f) { + data[1] = 0x81; + data[2] = origlen; + } else { + data[1] = origlen; + } + return data; +} + + static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag) { uint8_t *data = msgb_push(msgb, 2); @@ -59,83 +75,111 @@ 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);
-/* Send response to a mobile-originated ProcessUnstructuredSS-Request */ -int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, - const struct msgb *in_msg, const char *response_text, - const struct ussd_request *req) + data[0] = msgb->len - 1; + return data; +} + +/* 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"); - struct gsm48_hdr *gh; uint8_t *ptr8; - int response_len;
/* First put the payload text into the message */ ptr8 = msgb_put(msg, 0); - gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len); - msgb_put(msg, response_len); + + 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_TL(msg, ASN1_OCTET_STRING_TAG); + msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG);
/* Pre-pend the DCS octet string */ - msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_language);
/* Then wrap these as a Sequence */ - msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); - - /* Pre-pend the operation code */ - msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, - GSM0480_OP_CODE_PROCESS_USS_REQ); - - /* Wrap the operation code and IA5 string as a sequence */ - msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); + 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; +}
- /* Pre-pend the invoke ID */ - msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); +#ifndef NO_GSM0480_SEND_FUNC
- /* Wrap this up as a Return Result component */ - msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT); +int gsm0480_send_component(struct gsm_subscriber_connection *conn, + struct msgb *msg, + struct ss_header* reqhdr) +{ + struct gsm48_hdr *gh;
- /* Wrap the component in a Facility message */ - msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + 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 { + abort(); + }
/* 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 + gh->proto_discr = GSM48_PDISC_NC_SS | reqhdr->transaction_id | (1<<7); /* TI direction = 1 */ - gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + 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); }
+ 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_TL(msg, GSM0480_CTYPE_REJECT); - - /* Wrap the component in a Facility message */ - msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + msgb_wrap_with_ASN1_TL(msg, GSM0480_CTYPE_REJECT);
- /* And finally pre-pend the L3 header */ - gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); - gh->proto_discr = GSM48_PDISC_NC_SS; - gh->proto_discr |= req->transaction_id | (1<<7); /* TI direction = 1 */ - gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; - - return gsm0808_submit_dtap(conn, msg, 0, 0); + /* Prepare data for L3 header */ + ssh.transaction_id = transaction_id; + ssh.message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + return gsm0480_send_component(conn, msg, &ssh); }
int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text) @@ -173,3 +217,5 @@ int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn)
return gsm0808_submit_dtap(conn, msg, 0, 0); } + +#endif diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index 7f01eae..3cafe02 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -33,41 +33,71 @@ #include <openbsc/gsm_subscriber.h> #include <openbsc/debug.h> #include <openbsc/osmo_msc.h> +#include <openbsc/ussd.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0480.h>
/* Declarations of USSD strings to be recognised */ const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
/* Forward declarations of network-specific handler functions */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req); +static int send_own_number(struct gsm_subscriber_connection *conn, + const struct ss_header *reqhdr, + const struct ss_request *req);
/* Entrypoint - handler function common to all mobile-originated USSDs */ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) { int rc; - struct ussd_request req; + struct ss_header reqhdr; + struct ss_request req; + char request_string[MAX_LEN_USSD_STRING + 1]; struct gsm48_hdr *gh;
memset(&req, 0, sizeof(req)); + memset(&reqhdr, 0, sizeof(reqhdr)); gh = msgb_l3(msg); - rc = gsm0480_decode_ussd_request(gh, msgb_l3len(msg), &req); + rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &reqhdr); if (!rc) { - DEBUGP(DMM, "Unhandled SS\n"); - rc = gsm0480_send_ussd_reject(conn, msg, &req); + DEBUGP(DSS, "Incorrect SS header\n"); msc_release_connection(conn); return rc; }
- /* Release-Complete */ - if (req.text[0] == '\0') + 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; + } + + if (reqhdr.message_type == GSM0480_MTYPE_RELEASE_COMPLETE) 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); + 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; + } + + 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(DMM, "Unhandled USSD %s\n", req.text); - rc = gsm0480_send_ussd_reject(conn, msg, &req); + 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 */ @@ -76,12 +106,33 @@ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) }
/* A network-specific handler function */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req) +static int send_own_number(struct gsm_subscriber_connection *conn, + const struct ss_header *reqhdr, + const struct ss_request *req) { + struct ss_request rss; + struct ss_header rssh; + 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); - return gsm0480_send_ussd_response(conn, msg, response_string, req); + + 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); }
On Fri, Apr 22, 2016 at 02:41:32PM +0200, Sergey Kostanbaev wrote:
openbsc/include/openbsc/gsm_04_80.h | 15 +++-- openbsc/src/libmsc/gsm_04_80.c | 128 ++++++++++++++++++++++++------------ openbsc/src/libmsc/ussd.c | 79 ++++++++++++++++++---- 3 files changed, 162 insertions(+), 60 deletions(-)
diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h index 0a60652..371bc17 100644 --- a/openbsc/include/openbsc/gsm_04_80.h +++ b/openbsc/include/openbsc/gsm_04_80.h @@ -7,12 +7,17 @@
struct gsm_subscriber_connection;
-int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
const struct msgb *in_msg, const char* response_text,const struct ussd_request *req);+int gsm0480_send_component(struct gsm_subscriber_connection *conn,
Why remove the '_ussd_' name component here while you keep it in the other functions?
IMHO you should also describe function renames in the log message.
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);
...and additions/out-factorings as well.
^ osmo* is usually sparse on whitespace ;)
int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text); int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn); diff --git a/openbsc/src/libmsc/gsm_04_80.c b/openbsc/src/libmsc/gsm_04_80.c index f1d75f2..e6e4a92 100644 --- a/openbsc/src/libmsc/gsm_04_80.c +++ b/openbsc/src/libmsc/gsm_04_80.c @@ -39,6 +39,22 @@ #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;
- uint8_t *data = msgb_push(msgb, (origlen > 0x7f) ? 3 : 2);
- data[0] = tag;
- if (origlen > 0x7f) {
data[1] = 0x81;data[2] = origlen;- } else {
data[1] = origlen;- }
- return data;
+}
Would be good to use constants instead of the two magic values, which would also improve readability; otherwise I would like to see comments describing the semantics to the uninformed reader.
^ Again, a single line of whitespace would be usual in osmo*.
...
This patch is rather large, especially for a one-liner commit log; would it be possible to break this patch down into smaller changes that are easier to review? That would be great!
Unfortunately I have to redirect my attention to other tasks now and cannot look at the remaining changes...
Thanks! ~Neels
static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag) { uint8_t *data = msgb_push(msgb, 2); @@ -59,83 +75,111 @@ 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);
-/* Send response to a mobile-originated ProcessUnstructuredSS-Request */ -int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
const struct msgb *in_msg, const char *response_text,const struct ussd_request *req)
- data[0] = msgb->len - 1;
- return data;
+}
+/* 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");
struct gsm48_hdr *gh; uint8_t *ptr8;
int response_len;
/* First put the payload text into the message */ ptr8 = msgb_put(msg, 0);
gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len);
msgb_put(msg, response_len);
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_TL(msg, ASN1_OCTET_STRING_TAG);
msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG);
/* Pre-pend the DCS octet string */
- msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_language);
/* Then wrap these as a Sequence */
- msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
- /* Pre-pend the operation code */
- msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
GSM0480_OP_CODE_PROCESS_USS_REQ);- /* Wrap the operation code and IA5 string as a sequence */
- msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
- 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;
+}
- /* Pre-pend the invoke ID */
- msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+#ifndef NO_GSM0480_SEND_FUNC
- /* Wrap this up as a Return Result component */
- msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+int gsm0480_send_component(struct gsm_subscriber_connection *conn,
struct msgb *msg,struct ss_header* reqhdr)+{
- struct gsm48_hdr *gh;
- /* Wrap the component in a Facility message */
- msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
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 {
abort();}
/* 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
- gh->proto_discr = GSM48_PDISC_NC_SS | reqhdr->transaction_id | (1<<7); /* TI direction = 1 */
- gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
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);
}
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_TL(msg, GSM0480_CTYPE_REJECT);
- /* Wrap the component in a Facility message */
- msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
- msgb_wrap_with_ASN1_TL(msg, GSM0480_CTYPE_REJECT);
- /* And finally pre-pend the L3 header */
- gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
- gh->proto_discr = GSM48_PDISC_NC_SS;
- gh->proto_discr |= req->transaction_id | (1<<7); /* TI direction = 1 */
- gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
- return gsm0808_submit_dtap(conn, msg, 0, 0);
- /* Prepare data for L3 header */
- ssh.transaction_id = transaction_id;
- ssh.message_type = GSM0480_MTYPE_RELEASE_COMPLETE;
- return gsm0480_send_component(conn, msg, &ssh);
}
int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text) @@ -173,3 +217,5 @@ int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn)
return gsm0808_submit_dtap(conn, msg, 0, 0); }
+#endif diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index 7f01eae..3cafe02 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -33,41 +33,71 @@ #include <openbsc/gsm_subscriber.h> #include <openbsc/debug.h> #include <openbsc/osmo_msc.h> +#include <openbsc/ussd.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0480.h>
/* Declarations of USSD strings to be recognised */ const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
/* Forward declarations of network-specific handler functions */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req); +static int send_own_number(struct gsm_subscriber_connection *conn,
const struct ss_header *reqhdr,const struct ss_request *req);/* Entrypoint - handler function common to all mobile-originated USSDs */ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) { int rc;
- struct ussd_request req;
struct ss_header reqhdr;
struct ss_request req;
char request_string[MAX_LEN_USSD_STRING + 1]; struct gsm48_hdr *gh;
memset(&req, 0, sizeof(req));
memset(&reqhdr, 0, sizeof(reqhdr)); gh = msgb_l3(msg);
- rc = gsm0480_decode_ussd_request(gh, msgb_l3len(msg), &req);
- rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &reqhdr); if (!rc) {
DEBUGP(DMM, "Unhandled SS\n");rc = gsm0480_send_ussd_reject(conn, msg, &req);
msc_release_connection(conn); return rc; }DEBUGP(DSS, "Incorrect SS header\n");
- /* Release-Complete */
- if (req.text[0] == '\0')
- 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;- }
- if (reqhdr.message_type == GSM0480_MTYPE_RELEASE_COMPLETE) 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);
- 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;- }
- 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"); } else {rc = send_own_number(conn, &reqhdr, &req);
DEBUGP(DMM, "Unhandled USSD %s\n", req.text);rc = gsm0480_send_ussd_reject(conn, msg, &req);
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 */
@@ -76,12 +106,33 @@ int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) }
/* A network-specific handler function */ -static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req) +static int send_own_number(struct gsm_subscriber_connection *conn,
const struct ss_header *reqhdr,const struct ss_request *req){
struct ss_request rss;
struct ss_header rssh;
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);
- return gsm0480_send_ussd_response(conn, msg, response_string, req);
- 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);}
1.9.1
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
diff --git a/openbsc/include/openbsc/gsm_ussd_map.h b/openbsc/include/openbsc/gsm_ussd_map.h new file mode 100644 index 0000000..72798b2 --- /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 0000000..7faa5b8 --- /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 6ef1612..d82e576 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 2665468..b5b073a 100644 --- a/openbsc/include/openbsc/ussd.h +++ b/openbsc/include/openbsc/ussd.h @@ -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 diff --git a/openbsc/src/libmsc/Makefile.am b/openbsc/src/libmsc/Makefile.am index 5195890..566efe2 100644 --- a/openbsc/src/libmsc/Makefile.am +++ b/openbsc/src/libmsc/Makefile.am @@ -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 diff --git a/openbsc/src/libmsc/gsm_ussd_map.c b/openbsc/src/libmsc/gsm_ussd_map.c new file mode 100644 index 0000000..7ca84b1 --- /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 0000000..1d48efb --- /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/transaction.c b/openbsc/src/libmsc/transaction.c index a750362..89cb2b5 100644 --- a/openbsc/src/libmsc/transaction.c +++ b/openbsc/src/libmsc/transaction.c @@ -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) { diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index 3cafe02..4f90149 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -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_data.h | 1 + openbsc/src/libmsc/vty_interface_layer3.c | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+)
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h index 6d7aba3..5800df7 100644 --- a/openbsc/include/openbsc/gsm_data.h +++ b/openbsc/include/openbsc/gsm_data.h @@ -244,6 +244,7 @@ struct gsm_network { struct llist_head upqueue; struct llist_head trans_list; struct bsc_api *bsc_api; + struct gprs_gsup_client *ussd_sup_client;
unsigned int num_bts; struct llist_head bts_list; diff --git a/openbsc/src/libmsc/vty_interface_layer3.c b/openbsc/src/libmsc/vty_interface_layer3.c index 790fedf..1a5b518 100644 --- a/openbsc/src/libmsc/vty_interface_layer3.c +++ b/openbsc/src/libmsc/vty_interface_layer3.c @@ -48,6 +48,8 @@ #include <openbsc/sms_queue.h> #include <openbsc/mncc_int.h> #include <openbsc/handover.h> +#include <openbsc/gprs_gsup_client.h> +#include <openbsc/gsm_ussd_map.h>
#include <osmocom/vty/logging.h>
@@ -993,6 +995,30 @@ DEFUN(meas_feed_scenario, meas_feed_scenario_cmd, return CMD_SUCCESS; }
+DEFUN(sup_ussd_destination, sup_ussd_destination_cmd, + "sup-ussd destination ADDR <0-65535>", + "Enable SUP USSD socket to a given address/port" "destination\n" "address or hostname\n" "port number\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + if (gsmnet->ussd_sup_client) { + LOGP(DMM, 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]), &ussd_map_read_cb); + if (!gsmnet->ussd_sup_client) { + LOGP(DMM, 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; +} +
DEFUN(logging_fltr_imsi, logging_fltr_imsi_cmd, @@ -1130,6 +1156,7 @@ int bsc_vty_init_extra(void) install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd); install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd); install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd); + install_element(NITB_NODE, &sup_ussd_destination_cmd);
return 0; }
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
--- openbsc/src/ussd-proxy/Makefile.am | 17 + openbsc/src/ussd-proxy/ussd_proxy.c | 1544 +++++++++++++++++++++++++++++++++++ 2 files changed, 1561 insertions(+) create mode 100644 openbsc/src/ussd-proxy/Makefile.am create mode 100644 openbsc/src/ussd-proxy/ussd_proxy.c
diff --git a/openbsc/src/ussd-proxy/Makefile.am b/openbsc/src/ussd-proxy/Makefile.am new file mode 100644 index 0000000..85f3e64 --- /dev/null +++ b/openbsc/src/ussd-proxy/Makefile.am @@ -0,0 +1,17 @@ +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) diff --git a/openbsc/src/ussd-proxy/ussd_proxy.c b/openbsc/src/ussd-proxy/ussd_proxy.c new file mode 100644 index 0000000..a85e0fb --- /dev/null +++ b/openbsc/src/ussd-proxy/ussd_proxy.c @@ -0,0 +1,1544 @@ +#ifdef HAVE_CONFIG_H +//#include <config.h> +#endif + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#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 +#define SU_ROOT_MAGIC_T context_t +#define NUA_MAGIC_T context_t + +typedef struct operation operation_t; +#define NUA_HMAGIC_T operation_t + +#include <sofia-sip/nta.h> +#include <sofia-sip/nua.h> +#include <sofia-sip/sip_header.h> +#include <sofia-sip/sip_tag.h> +#include <sofia-sip/su_tag_io.h> +#include <sofia-sip/sl_utils.h> +#include <sofia-sip/sip_util.h> +#include <sofia-sip/auth_client.h> +#include <sofia-sip/tport_tag.h> +#include <sofia-sip/url.h> +#include <sofia-sip/su_log.h> + +#include <osmocom/abis/ipa.h> +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/gsm/gsm0480.h> +#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 uint32_t sup_tcap_tid_t; + + +typedef struct isup_connection isup_connection_t; + +struct isup_connection { + context_t *ctx; + + su_socket_t isup_conn_socket; + su_wait_t isup_conn_event; + int isup_register_idx; + + /* osmocom data */ + + struct msgb *pending_msg; +}; + +typedef enum ss_type { + TYPE_USSD, + TYPE_SS_OTHER +} ss_type_t; + +struct ussd_session { + isup_connection_t *conn; + sup_tcap_tid_t ref; + + int ms_originated; + char extention[32]; + + ss_type_t type; + + uint8_t ss_code; + struct ss_request rigester_msg; +}; + +struct context_s { + su_home_t home[1]; + su_root_t *root; + + su_socket_t isup_acc_socket; + su_wait_t isup_acc_event; + + nua_t *nua; + + url_t *to_url; + url_t *self_url; + + su_timer_t *timer; + su_duration_t max_ussd_ses_duration; + + /* iconv data */ + iconv_t* utf8_to_ucs2; + iconv_t* ucs2_to_utf8; + + iconv_t* utf8_to_latin1; + iconv_t* latin1_to_utf8; + + /* Array of isup connections */ + struct isup_connection isup[1]; + + /* list of active operations */ + struct llist_head operation_list; + unsigned operation_count; + unsigned operations_max; +}; + + +/* Example of operation handle context information structure */ +struct operation +{ + struct llist_head list; + + nua_handle_t *handle; /* operation handle */ + context_t *ctx; + su_time_t tm_initiated; + + /* protocol specific sessions */ + struct ussd_session ussd; +}; + +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, + uint8_t message_type, + const uint8_t *component, + uint8_t component_len, + uint32_t ref); + +static +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) +{ + sip_header_t *h = (sip_header_t *)sip->sip_unknown; + for (; h; h = (sip_header_t *)h->sh_succ) { + if (strcasecmp(h->sh_unknown->un_name, header) == 0) { + return h->sh_unknown->un_value; + } + } + return NULL; +} + + +int sup_server_send(isup_connection_t *conn, struct msgb *msg) +{ + ssize_t sz; + + if (!conn) { + msgb_free(msg); + return -ENOTCONN; + } + + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_GSUP); + ipa_msg_push_header(msg, IPAC_PROTO_OSMO); + + LOGP(DLCTRL, LOGL_ERROR, + "Sending wire, will send: %s\n", msgb_hexdump(msg)); + + // FIXME ugly hack!!! + // TODO place message in send queue !!!! + sz = send(conn->isup_conn_socket, msg->data, msg->len, 0); + msgb_free(msg); + + return ((unsigned)sz == msg->len) ? 0 : -1; +} + +static int ussd_parse_xml(const char *xml, + unsigned xml_len, + const char **lang, + unsigned *lang_len, + const char **msg, + unsigned *msg_len) +{ + /* Example of parsing XML + <?xml version="1.0" encoding="UTF-8"?> + <ussd-data> + <language>en</language> + <ussd-string>Test</ussd-string> + </ussd-data> + */ + + // <ussd-data> tag + char* ussd_data_stag = strstr(xml, "<ussd-data>"); + if (ussd_data_stag == NULL) + return 0; + + char* ussd_data_etag = strstr(ussd_data_stag, "</ussd-data>"); + if (ussd_data_etag == NULL) + return 0; + + // <language> tag + char* ussd_lang_stag = strstr(ussd_data_stag, "<language>"); + if (ussd_lang_stag == NULL) + return 0; + + char* ussd_lang_etag = strstr(ussd_lang_stag, "</language>"); + if (ussd_lang_etag == NULL) + return 0; + + // <language> tag + char* ussd_ussd_stag = strstr(ussd_data_stag, "<ussd-string>"); + if (ussd_ussd_stag == NULL) + return 0; + + char* ussd_ussd_etag = strstr(ussd_ussd_stag, "</ussd-string>"); + if (ussd_ussd_etag == NULL) + return 0; + + if (ussd_ussd_etag - xml > xml_len || ussd_lang_etag - xml > xml_len) + return 0; + + *lang = ussd_lang_stag + strlen("<language>"); + *lang_len = ussd_lang_etag - *lang; + + *msg = ussd_ussd_stag + strlen("<ussd-string>"); + *msg_len = ussd_ussd_etag - *msg; + + return 1; +} + +// Operation APIs +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.ref == ref) + return op; + } + return NULL; +} + +static operation_t* operation_alloc(context_t* ctx) +{ + operation_t* op; + + if (ctx->operation_count >= ctx->operations_max) { + fprintf(stderr, "!!! maximum number of active session is reached: %d\n", + ctx->operation_count); + return NULL; + } + + /* create operation context information */ + op = su_zalloc(ctx->home, (sizeof *op)); + if (!op) { + return NULL; + } + + op->ctx = ctx; + op->tm_initiated = su_now(); + INIT_LLIST_HEAD(&op->list); + llist_add_tail(&op->list, &ctx->operation_list); + ctx->operation_count++; + + return op; +} + +static void operation_destroy(operation_t* op) +{ + /* release operation handle */ + nua_handle_destroy(op->handle); + op->handle = NULL; + + llist_del(&op->list); + 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); +} + +void proxy_r_invite(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[]) +{ + fprintf(stderr, "*** Got reply %d for INVITE\n", status); + if (status == 200) { + nua_ack(nh, TAG_END()); + } 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.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); + } +} + +void proxy_i_bye(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* ri; + int rc; + // printf("*** call released:\n%s\n", sip->sip_payload->pl_data); + + ri = get_unknown_header(sip, "Recv-Info"); + if (ri && (strcasecmp(ri, "g.3gpp.ussd") == 0)) { + /* Parse XML */ + const char *language; + const char *msg; + unsigned language_len; + unsigned msg_len; + + if (ussd_parse_xml(sip->sip_payload->pl_data, + sip->sip_payload->pl_len, + &language, &language_len, + &msg, &msg_len)) { + printf("=== USSD (%.*s): %.*s\n", + language_len, language, + msg_len, msg); + + /* Send reply back to SUP */ + // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + rc = ussd_send_data(hmagic, 1, language, language_len, + msg, msg_len); + if (rc == 0) { + // Normal shutdown + operation_destroy(hmagic); + return; + } + + fprintf(stderr, "*** unable to send to SUP\n"); + } else { + fprintf(stderr, "*** unable to parse XML\n"); + } + } + + fprintf(stderr, "*** response BYE with %d satus is malformed, drop session\n", + status); + ussd_send_reject(hmagic->ussd.conn, + 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' + 10; + else if (a >= 'a' && a <= 'f') + return a-'a' + 10; + + fprintf(stderr, "*** Incorrect nibble deteced: %02x\n", a); + return 0xff; +} + +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(buffer) - 1; ) { + uint8_t hi_nibble = pl_txt[i++]; + if (hi_nibble == 0xff || i == pl_txt_len) + break; + uint8_t lo_nibble = pl_txt[i++]; + if (lo_nibble == 0xff) + break; + + buffer[buflen++] = (get_nibble(hi_nibble) << 4) | + get_nibble(lo_nibble); + } + + fprintf(stderr, "got bye_ss %d `%.*s` -> %d bytes\n", pl_txt_len, pl_txt_len, pl_txt, buflen); + + if (buflen > 1) { + /* ASN.1 length can be 1 or 2 bytes ( >2 isn't possible anyway here ) */ + unsigned len = (buffer[1] < 0x80) ? buffer[1] : ( (buflen > 2) ? buffer[2] : 0xff); + unsigned len_len = (buffer[1] < 0x80) ? 1 : buffer[1] - 0x80; + + if (len + 1 + len_len != buflen) { + fprintf(stderr, "*** parsed %d len, but should be %d (%s)", + buflen, len_len + 1 + len, pl_txt); + } + } + + ussd_send_data_ss(hmagic->ussd.conn, + GSM0480_MTYPE_RELEASE_COMPLETE, + buffer, + buflen, + hmagic->ussd.ref); + operation_destroy(hmagic); +} + +void proxy_r_bye(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[]) +{ + fprintf(stderr, "*** Got reply %d for BUY\n", status); + operation_destroy(hmagic); +} + +void proxy_i_error(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[]) +{ +#if 0 + if (!hmagic) { + return; + } + + fprintf(stderr, "*** error in session with %d satus\n", + status); + ussd_send_reject(hmagic->ussd.conn, + hmagic->ussd.rigester_msg.invoke_id, + hmagic->ussd.rigester_msg.opcode); + operation_destroy(hmagic); +#endif +} + +void proxy_info(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[], + int response) +{ + const char* ri; + int rc; + + // Normal ACK is recieved + if (response == 1 && status == 200) + return; + + ri = get_unknown_header(sip, "Recv-Info"); + if (ri && (strcasecmp(ri, "g.3gpp.ussd") == 0)) { + /* Parse XML */ + const char *language; + const char *msg; + unsigned language_len; + unsigned msg_len; + + if (ussd_parse_xml(sip->sip_payload->pl_data, + sip->sip_payload->pl_len, + &language, &language_len, + &msg, &msg_len)) { + printf("%s USSD (%.*s): %.*s\n", + (response) ? ">>>" : "<<<", + language_len, language, + msg_len, msg); + + if (hmagic == 0) { + printf("*** unknown session, ignoring"); + + // FIXME this function works only with a dialog! + nua_respond(nh, 481, "INFO with no session", TAG_END()); + return; + } + + /* Send reply back to SUP */ + // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + rc = ussd_send_data(hmagic, 0, language, language_len, + msg, msg_len); + if (rc == 0) + return; + + fprintf(stderr, "*** unable to send to SUP in INFO\n"); + } else { + fprintf(stderr, "*** unable to parse XML in INFO\n"); + } + } + + fprintf(stderr, "*** %s INFO with %d satus is malformed, drop session\n", + response ? "response" : "request", + status); + ussd_send_reject(hmagic->ussd.conn, + hmagic->ussd.ref, + hmagic->ussd.rigester_msg.invoke_id); + operation_destroy(hmagic); +} + +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" + "<language>%s</language>\n" + "<ussd-string>%.*s</ussd-string>\n" + "</ussd-data>", + language, + tmpbuf_utf8_len, tmpbuf_utf8); + if (content_len > max_len) { + content[max_len - 1] = 0; + return 0; + } + 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; + sip_to_t *from = NULL; + url_t to_url, from_url; + 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)); + + 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_latin1(ctx, content, sizeof(content), + decoded, decoded_len)) { + goto failed_to_parse_xml; + } + + + /* Destination address */ + 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); + 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_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"), + SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"), + SIPTAG_PAYLOAD_STR(content), + TAG_END()); + return 0; + +failed_create_handle: + if (from != NULL) + su_free(ctx->home, from); + if (to != NULL) + su_free(ctx->home, to); +failed_to_parse_xml: + fprintf(stderr, "*** open_ussd_session failed!\n"); + return -1; +} + +int ussd_session_facility(operation_t *op, + struct ss_request* ss, + const char* extention) +{ + char content[1024]; + 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; + } + + nua_info(op->handle, + /* other tags as needed ... */ + SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"), + SIPTAG_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"), + SIPTAG_PAYLOAD_STR(content), + TAG_END()); + + return 0; +} + +void context_callback(nua_event_t event, + 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[]) +{ + fprintf(stderr, "$$$ got event %d: status: %d (%s) : %p\n", event, status, phrase, hmagic); + + switch (event) { + case nua_i_error: + proxy_i_error(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_i_info: + if (hmagic->ussd.type == TYPE_USSD) + proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 0); + break; + + case nua_r_info: + if (hmagic->ussd.type == TYPE_USSD) + proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 1); + break; + + case nua_i_bye: + 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: + //app_i_invite(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_r_invite: + proxy_r_invite(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + case nua_r_bye: + proxy_r_bye(status, phrase, nua, magic, nh, hmagic, sip, tags); + break; + + default: + /* unknown event -> print out error message */ + if (status > 100) { + printf("unknown event %d: %03d %s\n", + event, + status, + phrase); + } else { + printf("unknown event %d\n", event); + } + tl_print(stdout, "", tags); + break; + } +} + +static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, size_t len) +{ + char extention[32] = {0}; + 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(data, len, &ss, &ref, extention, sizeof(extention))) { + LOGP(DLCTRL, LOGL_ERROR, "Can't parse uss message\n"); + 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 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 (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 (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 */ + op = operation_alloc(ctx); + if (op == NULL) { + LOGP(DLCTRL, LOGL_ERROR, "Unable to allocate new session\n"); + 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 (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 (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 (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, ref); + if (op == NULL) { + LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found\n", + ssreq.invoke_id); + goto err_send_reject; + } + + // TODO check result!! MO/MT error handling + rc = ussd_session_facility(op, &ssreq, extention); + if (rc < 0) { + operation_destroy(op); + goto err_send_reject; + } + break; + + case GSM0480_MTYPE_RELEASE_COMPLETE: + 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", + ssreq.invoke_id); + return 0; + } + + nua_bye(op->handle, TAG_END()); + break; + + default: + LOGP(DLCTRL, LOGL_ERROR, "Unknown message type 0x%02x\n", ss.message_type); + goto err_send_reject; + } + + return 0; + +err_send_reject: + 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, uint32_t ref, uint8_t invoke_id) +{ + uint8_t buffer[2+3+3]; + + buffer[0] = GSM0480_CTYPE_REJECT; + buffer[1] = 3+3; + + buffer[2] = GSM0480_COMPIDTAG_INVOKE_ID; + buffer[3] = 1; + buffer[4] = invoke_id; + + 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, + 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, + &hdr, + NULL, + ref, + component); + + LOGP(DLCTRL, LOGL_ERROR, + "Sending USS, will send: %s\n", msgb_hexdump(outmsg)); + + return sup_server_send(conn, outmsg); +} + +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) { + message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + ss.component_type = GSM0480_CTYPE_REJECT; + ss.opcode = op->ussd.rigester_msg.opcode; + } else if (last) { + message_type = GSM0480_MTYPE_RELEASE_COMPLETE; + ss.component_type = GSM0480_CTYPE_RETURN_RESULT; + ss.opcode = op->ussd.rigester_msg.opcode; + } else { + 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; + } + + ss.invoke_id = op->ussd.rigester_msg.invoke_id; + + if (msg) { + 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_ASN1_LEN_USSD_STRING; + + 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; + ss.ussd_text_len = (uint8_t*)outbuf - ss.ussd_text; + + } else { + int outlen; + size_t len = (msg_len > MAX_LEN_USSD_STRING) ? + MAX_LEN_USSD_STRING : msg_len; + memcpy(tmpbuf, msg, len); + tmpbuf[len] = 0; + + gsm_7bit_encode_n_ussd(ss.ussd_text, + MAX_ASN1_LEN_USSD_STRING, tmpbuf, &outlen); + ss.ussd_text_len = outlen; + ss.ussd_text_language = 0x0f; + } + } else { + ss.ussd_text_len = 0; + ss.ussd_text_language = 0x0f; + ss.ussd_text[0] = 0; + } + + 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, + su_timer_t *t, + su_timer_arg_t *arg) +{ + context_t *cli = (context_t*)arg; + su_time_t n = su_now(); + + operation_t *op, *tmp; + 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) { + 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); + } + } +} + +static int isup_handle_connection(context_t *cli, su_wait_t *w, void *p) +{ + int rc; + isup_connection_t *conn = (isup_connection_t*)p; + + int events = su_wait_events(w, conn->isup_conn_socket); + printf("*** connection; event=0x%x\n", events); + + if (events & (SU_WAIT_ERR | SU_WAIT_HUP)) { + printf("*** connection destroyed\n"); + goto err; + } else if (events & SU_WAIT_IN) { + /* Incoming data */ + + struct ipaccess_head *iph; + struct msgb *msg = NULL; + int ret = ipa_msg_recv_buffered(conn->isup_conn_socket, &msg, &conn->pending_msg); + if (ret <= 0) { + if (ret == -EAGAIN) + return 0; + if (ret == 0) + LOGP(DLCTRL, LOGL_INFO, "The control connection was closed\n"); + else + LOGP(DLCTRL, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret); + + goto err; + } + + iph = (struct ipaccess_head *) msg->data; + switch (iph->proto) + { + case IPAC_PROTO_IPACCESS: + if (msg->l2h[0] == IPAC_MSGT_PING) { + printf("*** got PING\n"); + msg->l2h[0] = IPAC_MSGT_PONG; + send(conn->isup_conn_socket, msg->data, ntohs(iph->len) + sizeof(struct ipaccess_head), 0); + msgb_free(msg); + conn->pending_msg = NULL; + return 0; + } + + LOGP(DLCTRL, LOGL_ERROR, "Unknown IPAC_PROTO_IPACCESS msg 0x%x\n", msg->l2h[0]); + goto err; + case IPAC_PROTO_OSMO: + // TODO callback + if (msg->l2h[1] == GPRS_GSUP_MSGT_USSD_MAP) { + LOGP(DLCTRL, LOGL_ERROR, + "Receive USS: %s\n", msgb_hexdump(msg)); + + rc = rx_sup_uss_message(conn, &msg->l2h[1], msgb_l2len(msg) - 1); + if (rc < 0) { + /* TODO raise reject !!!!!!! */ + /* release complete */ + } + + msgb_free(msg); + conn->pending_msg = NULL; + return 0; + } + + /* TODO: handle gprs_gsup_decode() for other types */ + + LOGP(DLCTRL, LOGL_ERROR, "Unknown IPAC_PROTO_OSMO GPRS_GSUP_MSGT_* 0x%x\n", msg->l2h[1]); + msgb_free(msg); + conn->pending_msg = NULL; + goto err; + default: + LOGP(DLCTRL, LOGL_ERROR, "Protocol mismatch. We got 0x%x\n", iph->proto); + goto err; + } + } + + return 0; + +err: + close(conn->isup_conn_socket); + conn->isup_conn_socket = INVALID_SOCKET; + + su_wait_destroy(w); + + msgb_free(conn->pending_msg); + conn->pending_msg = NULL; + //su_root_deregister(cli, cli->isup_register_idx); + return 0; +} + +static int isup_handle_accept(context_t *cli, su_wait_t *w, void *p) +{ + su_sockaddr_t aaddr; + su_socket_t connection; + socklen_t len = sizeof(aaddr); + int rc; + + connection = accept(cli->isup_acc_socket, &aaddr.su_sa, &len); + if (connection == INVALID_SOCKET) { + perror("can't accept isup socket"); + return 0; + } + + printf("*** accepted from %s:%d\n", + inet_ntoa(aaddr.su_sin.sin_addr), + ntohs(aaddr.su_sin.sin_port)); + + /* TODO manage isup connection list, but just now use the single connection */ + isup_connection_t *conn = cli->isup; + if (conn->isup_conn_socket != INVALID_SOCKET) { + fprintf(stderr, "--- Can't accept, there's another connection\n"); + su_close(connection); + return 0; + } + + conn->ctx = cli; + conn->isup_conn_socket = connection; + conn->pending_msg = NULL; + + su_wait_init(&conn->isup_conn_event); + rc = su_wait_create(&conn->isup_conn_event, + conn->isup_conn_socket, + SU_WAIT_IN | /*SU_WAIT_OUT | */ SU_WAIT_HUP | SU_WAIT_ERR); + + conn->isup_register_idx = su_root_register(cli->root, + &conn->isup_conn_event, + isup_handle_connection, + conn, + 0); + return 0; +} + +#define DIPA_USSD_PROXY 0 + +struct log_info_cat ipa_proxy_test_cat[] = { + [DIPA_USSD_PROXY] = { + .name = "DIPA_USSD_PROXY", + .description = "USSD_PROXY", + .color = "\033[1;35m", + .enabled = 1, + .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info ipa_proxy_test_log_info = { + .filter_fn = NULL, + .cat = ipa_proxy_test_cat, + .num_cat = ARRAY_SIZE(ipa_proxy_test_cat), +}; + + +static void Usage(char* progname) +{ + fprintf(stderr, "Usage:\n" + "%s [options]\n" + "Options\n" + " -p <port> TCP port to listen incoming SUP connection\n" + " (default: 8184)\n" + " -t <url> Destination SIP URL (default: sip:127.0.0.1:5060)\n" + " -u <url> User agent SIP URL (default: sip:127.0.0.1:5090)\n" + " -x <url> Proxy SIP URL (default: <none>)\n" + " -T Force using TCP instead trying UDP\n" + " -D <secs> Maximum period of open USSD session (default: 90)\n" + " -o <sessions> Maximum number of concurrent USSD sessions\n" + " (default: 200)\n" + " -l <0-9> sip sofia loglevel, 0 - none; 9 - max\n" + , progname); +} + +int main(int argc, char *argv[]) +{ + su_home_t *home; + context_t context[1] = {{{SU_HOME_INIT(context)}}}; + su_sockaddr_t listen_addr; + int rc; + int sup_port = 8184; + const char* to_str = "sip:127.0.0.1:5060"; + const char* url_str = "sip:127.0.0.1:5090"; + const char* proxy_str = NULL; + int force_tcp = 0; + int max_ussd_ses_secs = 90; + int max_op_limit = 200; + int sip_loglevel = 1; + int c; + + while ((c = getopt (argc, argv, "x:p:t:u:D:To:l:L7?")) != -1) { + switch (c) + { + case 'x': + proxy_str = optarg; + break; + case 'p': + sup_port = atoi(optarg); + break; + case 't': + to_str = optarg; + break; + case 'u': + url_str = optarg; + break; + case 'T': + force_tcp = 1; + break; + case 'D': + max_ussd_ses_secs = atoi(optarg); + break; + case 'o': + max_op_limit = atoi(optarg); + break; + case 'l': + sip_loglevel = atoi(optarg); + break; + case 'L': + fprintf(stderr, " -L is now obsolete, ignored\n"); + break; + case '7': + fprintf(stderr, " -7 is now obsolete, ignored\n"); + break; + case '?': + default: + Usage(argv[0]); + return 2; + } + } + + osmo_init_logging(&ipa_proxy_test_log_info); + + su_init(); + su_home_init(home = context->home); + + context->root = su_root_create(context); + + su_log_set_level(NULL, sip_loglevel); + + /* Disable threading */ + su_root_threading(context->root, 0); + + if (!context->root) { + fprintf(stderr, "Unable to initialize sip-sofia context\n"); + return 1; + } + + 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"); + context->ucs2_to_utf8=iconv_open("utf-8", "utf-16be"); + + if (context->utf8_to_ucs2 == NULL || context->ucs2_to_utf8 == NULL || + context->utf8_to_latin1 == NULL || context->latin1_to_utf8 == NULL) { + fprintf(stderr, "Unable to initialize iconv\n"); + return 1; + } + + context->isup_acc_socket = su_socket(AF_INET, SOCK_STREAM, 0); + if (context->isup_acc_socket == INVALID_SOCKET) { + perror("unable to create socket\n"); + return 1; + } + su_setblocking(context->isup_acc_socket, 0); + su_setreuseaddr(context->isup_acc_socket, 1); + + context->isup->isup_conn_socket = INVALID_SOCKET; + + memset(&listen_addr, 0, sizeof(listen_addr)); + listen_addr.su_sin.sin_family = AF_INET; + listen_addr.su_sin.sin_addr.s_addr = INADDR_ANY; + listen_addr.su_sin.sin_port = htons(sup_port); + + rc = bind(context->isup_acc_socket, &listen_addr.su_sa, sizeof(listen_addr.su_sin)); + if (rc < 0) { + perror("cannot bind socket\n"); + return 2; + } + + rc = listen(context->isup_acc_socket, 1); + if (rc < 0) { + perror("cannot bind socket\n"); + return 2; + } + + su_wait_init(&context->isup_acc_event); + su_wait_create(&context->isup_acc_event, context->isup_acc_socket, SU_WAIT_ACCEPT); + su_root_register(context->root, + &context->isup_acc_event, + isup_handle_accept, + NULL, + 0); + + context->to_url = url_make(home, to_str); + context->self_url = url_make(home, url_str); + + if (context->to_url == NULL) { + fprintf(stderr, "Unable to parse destination URL\n"); + return 1; + } + if (context->self_url == NULL) { + fprintf(stderr, "Unable to parse our (source) URL\n"); + return 1; + } + + context->nua = nua_create(context->root, + context_callback, + context, + NUTAG_URL(url_str), + NUTAG_ENABLEINVITE(1), + NUTAG_AUTOALERT(1), + NUTAG_SESSION_TIMER(0), + NUTAG_AUTOANSWER(0), + NUTAG_MEDIA_ENABLE(0), + NUTAG_ALLOW("INVITE, ACK, BYE, CANCEL, INFO"), + TAG_NULL()); + if (context->nua == NULL) { + fprintf(stderr, "Unable to initialize sip-sofia nua\n"); + return 1; + } + + + if (proxy_str) { + nua_set_params(context->nua, + NUTAG_PROXY(proxy_str), + TAG_NULL()); + } + + if (force_tcp) { + nua_set_params(context->nua, + NTATAG_UDP_MTU(10), + TAG_NULL()); + } + + INIT_LLIST_HEAD(&context->operation_list); + context->operation_count = 0; + context->operations_max = max_op_limit; + + su_timer_t* tm = su_timer_create(su_root_task(context->root), 2000); + if (tm == NULL) { + fprintf(stderr, "Unable to initialize sip-sofia timer\n"); + return 1; + } + rc = su_timer_run(tm, timer_function, context); + if (rc < 0) { + fprintf(stderr, "Unable to start sip-sofia timer\n"); + return 1; + } + context->timer = tm; + context->max_ussd_ses_duration = max_ussd_ses_secs * 1000l; + + su_root_run(context->root); + nua_destroy(context->nua); + + return 0; +} +
From: Sergey Kostanbaev Sergey.Kostanbaev@fairwaves.co
--- openbsc/configure.ac | 12 ++++++++++++ openbsc/src/Makefile.am | 2 +- openbsc/src/ussd-proxy/Makefile.am | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/openbsc/configure.ac b/openbsc/configure.ac index 60601fe..b53eca6 100644 --- a/openbsc/configure.ac +++ b/openbsc/configure.ac @@ -177,6 +177,17 @@ 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)
@@ -198,6 +209,7 @@ AC_OUTPUT( src/ipaccess/Makefile src/utils/Makefile src/gprs/Makefile + src/ussd-proxy/Makefile tests/Makefile tests/atlocal tests/gsm0408/Makefile diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am index 6f6174e..f12ed54 100644 --- a/openbsc/src/Makefile.am +++ b/openbsc/src/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS)
-SUBDIRS = libcommon libmgcp libbsc libmsc libtrau libfilter osmo-nitb osmo-bsc_mgcp utils ipaccess gprs +SUBDIRS = libcommon libmgcp libbsc libmsc libtrau libfilter osmo-nitb osmo-bsc_mgcp utils ipaccess gprs ussd-proxy
# Conditional modules if BUILD_NAT diff --git a/openbsc/src/ussd-proxy/Makefile.am b/openbsc/src/ussd-proxy/Makefile.am index 85f3e64..0f9413a 100644 --- a/openbsc/src/ussd-proxy/Makefile.am +++ b/openbsc/src/ussd-proxy/Makefile.am @@ -1,3 +1,4 @@ +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) \ @@ -15,3 +16,4 @@ ussd_proxy_LDADD = \ -lsofia-sip-ua \ $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \ $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS) +endif
--- openbsc/src/libmsc/Makefile.am | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/openbsc/src/libmsc/Makefile.am b/openbsc/src/libmsc/Makefile.am index 566efe2..92483b3 100644 --- a/openbsc/src/libmsc/Makefile.am +++ b/openbsc/src/libmsc/Makefile.am @@ -10,6 +10,9 @@ libmsc_a_SOURCES = auth.c \ db.c \ gsm_04_08.c gsm_04_11.c gsm_04_11_helper.c \ gsm_04_80.c \ + ../gprs/gsm_04_08_gprs.c \ + ../gprs/gprs_utils.c \ + ../gprs/gprs_gsup_messages.c ../gprs/gprs_gsup_client.c \ gsm_subscriber.c \ mncc.c mncc_builtin.c mncc_sock.c \ rrlp.c \
--- openbsc/configure.ac | 1 + openbsc/tests/Makefile.am | 2 +- openbsc/tests/ussd/Makefile.am | 12 +++ openbsc/tests/ussd/ss_test.c | 199 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 openbsc/tests/ussd/Makefile.am create mode 100644 openbsc/tests/ussd/ss_test.c
diff --git a/openbsc/configure.ac b/openbsc/configure.ac index b53eca6..6ea83de 100644 --- a/openbsc/configure.ac +++ b/openbsc/configure.ac @@ -229,6 +229,7 @@ AC_OUTPUT( tests/oap/Makefile tests/gtphub/Makefile tests/mm_auth/Makefile + tests/ussd/Makefile doc/Makefile doc/examples/Makefile Makefile) diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am index 09298a3..5732671 100644 --- a/openbsc/tests/Makefile.am +++ b/openbsc/tests/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = gsm0408 db channel mgcp gprs abis gbproxy trau subscr mm_auth +SUBDIRS = gsm0408 db channel mgcp gprs abis gbproxy trau subscr mm_auth ussd
if BUILD_NAT SUBDIRS += bsc-nat bsc-nat-trie diff --git a/openbsc/tests/ussd/Makefile.am b/openbsc/tests/ussd/Makefile.am new file mode 100644 index 0000000..ecaa3db --- /dev/null +++ b/openbsc/tests/ussd/Makefile.am @@ -0,0 +1,12 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOABIS_CFLAGS) +noinst_PROGRAMS = ss_test + +# EXTRA_DIST = ss_test.ok + +ss_test_SOURCES = ss_test.c +ss_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -ldbi diff --git a/openbsc/tests/ussd/ss_test.c b/openbsc/tests/ussd/ss_test.c new file mode 100644 index 0000000..a59bd1b --- /dev/null +++ b/openbsc/tests/ussd/ss_test.c @@ -0,0 +1,199 @@ +/* simple test for the gsm0480 */ +/* + * 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 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#include <arpa/inet.h> + + +#include <openbsc/gsm_ussd_map.h> +#include <openbsc/gsm_ussd_map_proto.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/debug.h> +#include <osmocom/core/application.h> + +#define COMPARE(result, op, value) \ + if (!((result) op (value))) {\ + fprintf(stderr, "Compare failed. Was %x should be %x in %s:%d\n",result, value, __FILE__, __LINE__); \ + exit(-1); \ + } + +#define COMPARE_STR(result, value) \ + if (strcmp(result, value) != 0) { \ + fprintf(stderr, "Compare failed. Was %s should be %s in %s:%d\n",result, value, __FILE__, __LINE__); \ + exit(-1); \ + } + +#define DBG(...) + +#define VERIFY(res, cmp, wanted) \ + if (!(res cmp wanted)) { \ + printf("ASSERT failed: %s:%d Wanted: %d %s %d\n", \ + __FILE__, __LINE__, res, # cmp, wanted); \ + } + +const uint8_t test_ss_int_01[] = { +0xa1, 0x0e, 0x02, 0x01, 0x00, 0x02, 0x01, 0x0e, 0x30, 0x06, 0x04, 0x01, 0x21, 0x83, 0x01, 0x10 +}; + +const uint8_t test_ss_int_02[] = { +0xa1, 0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x0e, 0x30, 0x03, 0x04, 0x01, 0x21 +}; + +const uint8_t test_ss_int_03[] = { +0xa1, 0x17, 0x02, 0x01, 0x01, 0x02, 0x01, 0x0a, 0x30, 0x0f, 0x04, 0x01, 0x21, +0x83, 0x01, 0x10, 0x84, 0x07, 0x91, 0x52, 0x75, 0x89, 0x46, 0x36, 0x25 +}; + +const uint8_t test_ussd_01[] = { +0xa1, 0x81, 0x83, 0x02, 0x01, 0x01, 0x02, 0x01, 0x3c, 0x30, 0x7b, 0x04, 0x01, +0x0f, 0x04, 0x76, 0xd3, 0x66, 0x50, 0x4a, 0x55, 0xc4, 0x5c, 0x20, 0x6b, 0xda, +0x5c, 0x97, 0xd7, 0xe7, 0xe8, 0x34, 0xc8, 0x9e, 0x0f, 0x83, 0x68, 0x47, 0x50, +0xd2, 0x4d, 0x0f, 0xbb, 0xcb, 0xf4, 0xb4, 0x42, 0xe6, 0x02, 0x59, 0xd3, 0xe6, +0xba, 0xbc, 0x3e, 0x47, 0xa7, 0x41, 0xd6, 0x7c, 0x18, 0x34, 0x6d, 0x06, 0xa9, +0xc9, 0x65, 0x50, 0x31, 0x73, 0x81, 0xac, 0x69, 0x73, 0x5d, 0x5e, 0x9f, 0xa3, +0xd3, 0x20, 0x7b, 0x3e, 0x0c, 0x5a, 0xa7, 0xdb, 0x61, 0x7a, 0x38, 0x6d, 0x0e, +0xbb, 0x14, 0x34, 0x17, 0x68, 0xda, 0x0e, 0xcb, 0xe9, 0xa0, 0x6b, 0xb9, 0xbc, +0x2e, 0xbb, 0xc9, 0x8a, 0x9a, 0x0b, 0xd4, 0x4c, 0xb7, 0xd3, 0x20, 0x77, 0x18, +0x74, 0x2d, 0xdf, 0xcb, 0x20, 0x28, 0xbb, 0x3e, 0x57, 0xd8, 0x5c, 0x20, 0x7d, +0x38, 0x4d, 0x4e, 0x03 +}; + +const uint8_t test_ussd_02[] = { +0xa2, 0x10, 0x02, 0x01, 0x01, 0x30, 0x0b, 0x02, 0x01, 0x3c, 0x30, +0x06, 0x04, 0x01, 0x0f, 0x04, 0x01, 0x34 +}; + +static void test_uss_ser_deser(void) +{ + const uint32_t ref = 1234567890; + const char* ext = "555333444"; + struct msgb *data = msgb_alloc(4000, "test"); + struct ss_header ssh; + + uint32_t r_ref; + struct ss_header r_ssh; + char r_ext_buf[32]; + + int rc; + printf("testing serializing-deserializing\n"); + + ssh.component_length = sizeof(test_ussd_01); + ssh.component_offset = 0; + ssh.message_type = GSM0480_MTYPE_REGISTER; + ssh.transaction_id = 0; + + rc = subscr_uss_message(data, &ssh, ext, ref, test_ussd_01); + OSMO_ASSERT(rc == 0); + + printf("uss len:%d\n", msgb_length(data)); + + rc = rx_uss_message_parse(data->data, msgb_length(data), &r_ssh, &r_ref, r_ext_buf, sizeof(r_ext_buf)); + OSMO_ASSERT(rc == 0); + OSMO_ASSERT(ref == r_ref); + OSMO_ASSERT(ssh.message_type == r_ssh.message_type); + + rc = strcmp(ext, r_ext_buf); + printf("orig_ext:%s decoded_ext:%s\n", ext, r_ext_buf); + OSMO_ASSERT(rc == 0); + + printf("orig_clen:%d decoded_clen:%d\n", ssh.component_length, r_ssh.component_length); + OSMO_ASSERT(ssh.component_length == r_ssh.component_length); + + rc = memcmp(data->data + r_ssh.component_offset, test_ussd_01, sizeof(test_ussd_01)); + OSMO_ASSERT(rc == 0); + + + msgb_reset(data); + memset(&r_ssh, 0, sizeof(r_ssh)); + printf("testing serializing-deserializing small\n"); + + rc = subscr_uss_message(data, &ssh, NULL, ref, NULL); + OSMO_ASSERT(rc == 0); + + printf("uss len:%d\n", msgb_length(data)); + + rc = rx_uss_message_parse(data->data, msgb_length(data), &r_ssh, &r_ref, NULL, 0); + OSMO_ASSERT(rc == 0); + OSMO_ASSERT(ref == r_ref); + OSMO_ASSERT(ssh.message_type == r_ssh.message_type); + +} + +static void test_parse_ss(void) +{ + struct ss_request ss; + int rc; + printf("testing parsing ss\n"); + + // mark as uninitialized + memset(&ss, 0xcc, sizeof(ss)); + rc = gsm0480_parse_ss_facility(test_ussd_01, sizeof(test_ussd_01), &ss); + OSMO_ASSERT(rc == 1); + printf("Sample: test_ussd_01 ctype=%02x invoke_id=%02x opcode=%02x\n", + ss.component_type, ss.invoke_id, ss.opcode); + printf("- USSD: len:%d lang:%d\n", ss.ussd_text_len, ss.ussd_text_language); + + // mark as uninitialized + memset(&ss, 0xcc, sizeof(ss)); + rc = gsm0480_parse_ss_facility(test_ussd_02, sizeof(test_ussd_02), &ss); + OSMO_ASSERT(rc == 1); + printf("Sample: test_ussd_02 ctype=%02x invoke_id=%02x opcode=%02x\n", + ss.component_type, ss.invoke_id, ss.opcode); + + // mark as uninitialized + memset(&ss, 0xcc, sizeof(ss)); + rc = gsm0480_parse_ss_facility(test_ss_int_02, sizeof(test_ss_int_02), &ss); + OSMO_ASSERT(rc == 1); + printf("Sample: test_ss_int_02 ctype=%02x invoke_id=%02x opcode=%02x\n", + ss.component_type, ss.invoke_id, ss.opcode); + printf("- SS: code:%d\n", ss.ss_code); + + // mark as uninitialized + memset(&ss, 0xcc, sizeof(ss)); + rc = gsm0480_parse_ss_facility(test_ss_int_01, sizeof(test_ss_int_01), &ss); + OSMO_ASSERT(rc == 1); + printf("Sample: test_ss_int_01 ctype=%02x invoke_id=%02x opcode=%02x\n", + ss.component_type, ss.invoke_id, ss.opcode); + printf("- SS: code:%d\n", ss.ss_code); + + // mark as uninitialized + memset(&ss, 0xcc, sizeof(ss)); + rc = gsm0480_parse_ss_facility(test_ss_int_03, sizeof(test_ss_int_03), &ss); + OSMO_ASSERT(rc == 1); + printf("Sample: test_ss_int_01 ctype=%02x invoke_id=%02x opcode=%02x\n", + ss.component_type, ss.invoke_id, ss.opcode); + printf("- SS: code:%d\n", ss.ss_code); + +} + +int main(int argc, char **argv) +{ + osmo_init_logging(&log_info); + log_set_log_level(osmo_stderr_target, LOGL_INFO); + + test_uss_ser_deser(); + test_parse_ss(); + + printf("Done.\n"); + return EXIT_SUCCESS; +}