From 012c9203e46a359f0178b45e0bc936bcd044ad00 Mon Sep 17 00:00:00 2001 From: Sergey Kostanbaev Date: Fri, 30 Oct 2015 15:14:49 +0300 Subject: ussd: handle UCS-2 coding --- openbsc/include/openbsc/gsm_04_80.h | 10 ++- openbsc/src/libmsc/gsm_04_80.c | 35 +++++------ openbsc/src/libmsc/gsm_sup.c | 12 ++-- openbsc/src/libmsc/ussd.c | 2 + openbsc/src/reg-proxy/ussd_proxy.c | 120 ++++++++++++++++++++++++++++++++---- 5 files changed, 137 insertions(+), 42 deletions(-) diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h index 959eb7bff..68de65a0f 100644 --- a/openbsc/include/openbsc/gsm_04_80.h +++ b/openbsc/include/openbsc/gsm_04_80.h @@ -8,8 +8,14 @@ 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, uint8_t code, uint8_t ctype, uint8_t mtype); + const struct msgb *in_msg, + int response_text_len, + uint8_t response_lang, + const char* response_text, + const struct ussd_request *req, + uint8_t code, + uint8_t ctype, + uint8_t mtype); int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *request); diff --git a/openbsc/src/libmsc/gsm_04_80.c b/openbsc/src/libmsc/gsm_04_80.c index 4350557b4..a8cf3e9bf 100644 --- a/openbsc/src/libmsc/gsm_04_80.c +++ b/openbsc/src/libmsc/gsm_04_80.c @@ -78,7 +78,10 @@ static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, /* 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 msgb *in_msg, + int response_text_len, + uint8_t response_lang, + const char *response_text, const struct ussd_request *req, uint8_t code, uint8_t ctype, @@ -89,33 +92,23 @@ int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, uint8_t *ptr8; int response_len; -#if 1 - /* First put the payload text into the message */ ptr8 = msgb_put(msg, 0); - gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len); - msgb_put(msg, response_len); - /* Then wrap it as an Octet String */ - msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG); - - /* Pre-pend the DCS octet string */ - msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); -#else - response_len = strlen(response_text); - if (response_len > MAX_LEN_USSD_STRING) - response_len = MAX_LEN_USSD_STRING; - - ptr8 = msgb_put(msg, 0); - memcpy(ptr8, response_text, response_len); - msgb_put(msg, response_len); + if (response_text_len < 0) { + /* First put the payload text into the message */ + gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len); + msgb_put(msg, response_len); + response_lang = 0x0F; + } else { + memcpy(ptr8, response_text, response_text_len); + msgb_put(msg, response_text_len); + } /* Then wrap it as an Octet String */ msgb_wrap_with_ASN1_TL(msg, ASN1_OCTET_STRING_TAG); /* Pre-pend the DCS octet string */ - msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0xf4); -#endif - + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, response_lang); /* Then wrap these as a Sequence */ msgb_wrap_with_ASN1_TL(msg, GSM_0480_SEQUENCE_TAG); diff --git a/openbsc/src/libmsc/gsm_sup.c b/openbsc/src/libmsc/gsm_sup.c index da65977b3..e4dc7a9ca 100644 --- a/openbsc/src/libmsc/gsm_sup.c +++ b/openbsc/src/libmsc/gsm_sup.c @@ -58,8 +58,7 @@ static int subscr_uss_message(struct msgb *msg, msgb_tlv_put(msg, GSM0480_OPERATION_CODE, 1, &req->opcode); if (req->ussd_text_len > 0) { - //msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, 1, &req->ussd_text_language); - msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_len, req->ussd_text); + msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_len + 1, &req->ussd_text_language); } if (extention) { @@ -70,7 +69,7 @@ static int subscr_uss_message(struct msgb *msg, } /* fill actual length */ - gsup_indicator[3] = 3 + 3 + (req->ussd_text_len + 2) + (bcd_len + 2);; + gsup_indicator[3] = 3 + 3 + (req->ussd_text_len + 1 + 2) + (bcd_len + 2);; /* wrap with GSM0480_CTYPE_INVOKE */ // gsm0480_wrap_invoke(msg, req->opcode, invoke_id); @@ -131,9 +130,10 @@ static int rx_uss_message_parse(struct ss_request *ss, uint8_t len; switch (*const_data) { case ASN1_OCTET_STRING_TAG: - len = *(++const_data); - strncpy((char*)ss->ussd_text, - (const char*)++const_data, + 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; diff --git a/openbsc/src/libmsc/ussd.c b/openbsc/src/libmsc/ussd.c index 97e22b86a..b31f3e1a0 100644 --- a/openbsc/src/libmsc/ussd.c +++ b/openbsc/src/libmsc/ussd.c @@ -173,6 +173,8 @@ int on_ussd_response(const struct ss_request *req, const char *extention) if (req->component_type != GSM0480_CTYPE_REJECT) { rc = gsm0480_send_ussd_response(ussdq->conn, NULL, + (req->ussd_text_language == 0x80) ? -1 : req->ussd_text_len, + req->ussd_text_language, (const char *)req->ussd_text, &ussd_req, req->opcode, diff --git a/openbsc/src/reg-proxy/ussd_proxy.c b/openbsc/src/reg-proxy/ussd_proxy.c index e8fcab0e6..3e8f14020 100644 --- a/openbsc/src/reg-proxy/ussd_proxy.c +++ b/openbsc/src/reg-proxy/ussd_proxy.c @@ -39,6 +39,8 @@ typedef struct operation operation_t; #include +#include + typedef uint8_t sup_tcap_tid_t; /******************************************************************************/ @@ -84,9 +86,10 @@ static int rx_uss_message_parse(struct ss_request *ss, uint8_t len; switch (*const_data) { case ASN1_OCTET_STRING_TAG: - ss->ussd_text_len = len = *(++const_data); - strncpy((char*)ss->ussd_text, - (const char*)++const_data, + 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; @@ -130,8 +133,7 @@ static int subscr_uss_message(struct msgb *msg, msgb_tlv_put(msg, GSM0480_OPERATION_CODE, 1, &req->opcode); if (req->ussd_text_len > 0) { - //msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, 1, &req->ussd_text_language); - msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_len, req->ussd_text); + msgb_tlv_put(msg, ASN1_OCTET_STRING_TAG, req->ussd_text_len + 1, &req->ussd_text_language); } if (extention) { @@ -142,7 +144,7 @@ static int subscr_uss_message(struct msgb *msg, } /* fill actual length */ - gsup_indicator[3] = 3 + 3 + (req->ussd_text_len + 2) + (bcd_len + 2); + gsup_indicator[3] = 3 + 3 + (req->ussd_text_len + 1 + 2) + (bcd_len + 2); /* wrap with GSM0480_CTYPE_INVOKE */ // gsm0480_wrap_invoke(msg, req->opcode, invoke_id); @@ -194,6 +196,15 @@ struct context_s { 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; + + int dont_encode_in_latin1; + /* Array of isup connections */ struct isup_connection isup[1]; @@ -656,7 +667,6 @@ failed_to_parse_xml: } int ussd_session_facility(operation_t *op, - isup_connection_t *conn, struct ss_request* ss, const char* extention) { @@ -668,11 +678,10 @@ int ussd_session_facility(operation_t *op, return -1; } - // TODO add to header - // Recv-Info: g.3gpp.ussd 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()); @@ -754,6 +763,14 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, switch (ss.message_type) { case GSM0480_MTYPE_REGISTER: + if (ss.component_type != GSM0480_CTYPE_INVOKE) { + LOGP(DLCTRL, LOGL_ERROR, "Non-INVOKE component type in REGISTER: 0x%02x\n", ss.component_type); + goto err_send_reject; + } + if (ss.opcode != GSM0480_OP_CODE_PROCESS_USS_REQ) { + LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ss.opcode); + goto err_send_reject; + } /* Create new session */ op = operation_alloc(ctx); if (op == NULL) { @@ -774,6 +791,23 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, break; case GSM0480_MTYPE_FACILITY: + //Only MS-originated Menu session is supported, so we ignore INVOKE here + if (ss.component_type != GSM0480_CTYPE_RETURN_RESULT && + ss.component_type != GSM0480_CTYPE_RETURN_ERROR && + ss.component_type != GSM0480_CTYPE_REJECT) { + LOGP(DLCTRL, LOGL_ERROR, "Non-{RESULT/RETURN_ERROR/REJECT} component type in FACILITY: 0x%02x\n", ss.component_type); + goto err_send_reject; + } + // ///////////////////////////////////////////////// + // TODO handle RETURN_ERROR/REJECT + if (ss.component_type != GSM0480_CTYPE_RETURN_RESULT) { + LOGP(DLCTRL, LOGL_ERROR, "Component type in FACILITY: 0x%02x is not implemented yet\n", ss.component_type); + goto err_send_reject; + } + if (ss.opcode != GSM0480_OP_CODE_USS_REQUEST) { + LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ss.opcode); + goto err_send_reject; + } op = operation_find_by_tid(ctx, ss.invoke_id); if (op == NULL) { LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found\n", @@ -782,7 +816,7 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, } // TODO check result!! MO/MT error handling - rc = ussd_session_facility(op, sup_conn, &ss, extention); + rc = ussd_session_facility(op, &ss, extention); if (rc < 0) { operation_destroy(op); goto err_send_reject; @@ -794,7 +828,7 @@ static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, if (op == NULL) { LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found for RELEASE_COMPLETE\n", ss.invoke_id); - return; + return 0; } nua_bye(op->handle, TAG_END()); @@ -838,6 +872,16 @@ int ussd_send_data_ss(isup_connection_t *conn, struct ss_request* reply) return sup_server_send(conn, outmsg); } +static int is_string_ascii(const char* msg, unsigned msg_len) +{ + unsigned i; + for (i = 0; i < msg_len; ++i) { + if (*((uint8_t*)(msg++)) >= 0x80) + return 0; + } + return 1; +} + int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len, const char* msg, unsigned msg_len) { @@ -866,11 +910,44 @@ int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_le if (msg_len > MAX_LEN_USSD_STRING) { msg_len = MAX_LEN_USSD_STRING; } + if (is_string_ascii(msg, msg_len)) { + // GSM 7-bit coding, done on the other end of SUP + ss.ussd_text_len = msg_len; + ss.ussd_text_language = 0x80; + strncpy((char*)ss.ussd_text, msg, msg_len); + } else { + char* inbuf = (char*)msg; + size_t inleft = msg_len; + char* outbuf = (char*)ss.ussd_text; + size_t outleft = MAX_LEN_USSD_STRING; + size_t s; + // First of all try latin1 + if (op->ctx->dont_encode_in_latin1) { + s =(size_t)-1; + } else { + s = iconv(op->ctx->utf8_to_latin1, + &inbuf, &inleft, + &outbuf, &outleft); + } + if (s == (size_t)-1) { + s = iconv(op->ctx->utf8_to_ucs2, + &inbuf, &inleft, + &outbuf, &outleft); + if (s == (size_t)-1) { + perror("can't convert string from utf8"); + } + // UCS-2 encoding + ss.ussd_text_language = 0x48; + } else { + // 8-bit DATA encoding + ss.ussd_text_language = 0x44; + } + ss.ussd_text_len = (uint8_t*)outbuf - ss.ussd_text; - ss.ussd_text_len = msg_len; - strncpy((char*)ss.ussd_text, msg, msg_len); + } } else { ss.ussd_text_len = 0; + ss.ussd_text_language = 0x80; ss.ussd_text[0] = 0; } @@ -1065,6 +1142,7 @@ static void Usage(char* progname) " -o Maximum number of concurrent USSD sessions\n" " (default: 200)\n" " -l <0-9> sip sofia loglevel, 0 - none; 9 - max\n" + " -L Do not try to encode in 8-bit (use 7-bit or UCS-2)\n" , progname); } @@ -1081,6 +1159,7 @@ int main(int argc, char *argv[]) int max_ussd_ses_secs = 90; int max_op_limit = 200; int sip_loglevel = 1; + int dont_try_latin1 = 0; int c; while ((c = getopt (argc, argv, "p:t:u:D:To:l:?")) != -1) { @@ -1107,6 +1186,9 @@ int main(int argc, char *argv[]) case 'l': sip_loglevel = atoi(optarg); break; + case 'L': + dont_try_latin1 = 1; + break; case '?': default: Usage(argv[0]); @@ -1131,6 +1213,18 @@ int main(int argc, char *argv[]) return 1; } + context->dont_encode_in_latin1 = dont_try_latin1; + 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"); -- cgit v1.2.3