From patchwork Fri Apr 22 12:41:35 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergey Kostanbaev X-Patchwork-Id: 613589 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.osmocom.org (lists.osmocom.org [144.76.43.76]) by ozlabs.org (Postfix) with ESMTP id 3qrwHC53hwz9t3v for ; Fri, 22 Apr 2016 22:41:59 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=tUjFvlsF; dkim-atps=neutral Received: from lists.osmocom.org (lists.osmocom.org [144.76.43.76]) by lists.osmocom.org (Postfix) with ESMTP id 09375131F3; Fri, 22 Apr 2016 12:41:59 +0000 (UTC) X-Original-To: openbsc@lists.osmocom.org Delivered-To: openbsc@lists.osmocom.org Received: from mail-wm0-x232.google.com (mail-wm0-x232.google.com [IPv6:2a00:1450:400c:c09::232]) by lists.osmocom.org (Postfix) with ESMTP id B2F01131CC for ; Fri, 22 Apr 2016 12:41:55 +0000 (UTC) Received: by mail-wm0-x232.google.com with SMTP id n3so25119867wmn.0 for ; Fri, 22 Apr 2016 05:41:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=DwRUJmQANlm+4u/5WroEJGjHFLNXLDliJbHE4OJ/pxc=; b=tUjFvlsFL/dH21IZW3o5Jihzabu5jkTVbMbiju+KS1mwnb7RhTe3I4gCxmRhuYChPe ztbgD6RjrdZKqJW60fN4zAw0TEkK7DEbKvyRkUbGDUuBcMKOXwLUq8073J2jmGtNfeQZ 98h0R93w3Z1bdFrjbp00Ygc4shMKgS6aoo1t0TR1YF3nh6ApXGl9o50r6hhPQKcjDhxQ BNAnttBvgcItYh7cSMLHprVaY1FhCWwrEO8VDyuOYNX0nNDyrhat9uL3poiEbZL44M3Y Azxr52Ci55ZBCPFdjTOuf6L4qDj0eKUaHU/hP/UGwpVFOsLCR3bwpccrGW8wTH83ZctC qIIA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=DwRUJmQANlm+4u/5WroEJGjHFLNXLDliJbHE4OJ/pxc=; b=fhIxlL4LLrjsIzBF7X2CCKiEEOMXABbEflwrReXeswGZOvcUPXKCusGIsjD/UgtyWW VZ8iVW/oiZUZ/U4ZWyC611s2kMjlCn1bYKHyWweye4uo8S5buaz7843hLw/sXyWtBNH6 H3Aw5fj7VLyAywwtfmEowTYFaFYZXtxsker+27gnMWUr61nkQ/qoBaTbZn+JXLb1mUl2 06HPKkdDysw7TlPwlP0hc9XDk7qFb4FyFChr49DzLSxWwG3vG9Z7D04sjKKe6Cy6eNzD qHMYsCui1Mz8tFiIy9R/Aq1KZ6kT/hKet5Jn6QuhCgdyo1G3boT/J5T3AHz0GDtVTkdB psig== X-Gm-Message-State: AOPr4FWEL0PbSEiXQ+OwLWRflSb/uZC1pRjuVtMS9cg+v05L5AiJgODSd0gAzYVh477mSA== X-Received: by 10.28.234.11 with SMTP id i11mr3701354wmh.76.1461328914835; Fri, 22 Apr 2016 05:41:54 -0700 (PDT) Received: from serg-X55SR.in-berlin.de ([185.11.247.108]) by smtp.gmail.com with ESMTPSA id b15sm3224216wmd.1.2016.04.22.05.41.53 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 22 Apr 2016 05:41:53 -0700 (PDT) From: Sergey Kostanbaev To: openbsc@lists.osmocom.org Subject: [PATCH 6/9] ussd_proxy: Add ussd_proxy utility converting SUP USSD MAP to SIP IMS Date: Fri, 22 Apr 2016 14:41:35 +0200 Message-Id: <1461328898-8298-6-git-send-email-sergey.kostanbaev@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1461328898-8298-1-git-send-email-sergey.kostanbaev@gmail.com> References: <1461328898-8298-1-git-send-email-sergey.kostanbaev@gmail.com> X-BeenThere: openbsc@lists.osmocom.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: "Development of OpenBSC, OsmoBSC, OsmoNITB, OsmoCSCN" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Sergey Kostanbaev Errors-To: openbsc-bounces@lists.osmocom.org Sender: "OpenBSC" From: Sergey Kostanbaev --- 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +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 + + + en + Test + + */ + + // tag + char* ussd_data_stag = strstr(xml, ""); + if (ussd_data_stag == NULL) + return 0; + + char* ussd_data_etag = strstr(ussd_data_stag, ""); + if (ussd_data_etag == NULL) + return 0; + + // tag + char* ussd_lang_stag = strstr(ussd_data_stag, ""); + if (ussd_lang_stag == NULL) + return 0; + + char* ussd_lang_etag = strstr(ussd_lang_stag, ""); + if (ussd_lang_etag == NULL) + return 0; + + // tag + char* ussd_ussd_stag = strstr(ussd_data_stag, ""); + if (ussd_ussd_stag == NULL) + return 0; + + char* ussd_ussd_etag = strstr(ussd_ussd_stag, ""); + 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(""); + *lang_len = ussd_lang_etag - *lang; + + *msg = ussd_ussd_stag + strlen(""); + *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, + "\n" + "\n" + "%s\n" + "%.*s\n" + "", + 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 TCP port to listen incoming SUP connection\n" + " (default: 8184)\n" + " -t Destination SIP URL (default: sip:127.0.0.1:5060)\n" + " -u User agent SIP URL (default: sip:127.0.0.1:5090)\n" + " -x Proxy SIP URL (default: )\n" + " -T Force using TCP instead trying UDP\n" + " -D Maximum period of open USSD session (default: 90)\n" + " -o 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; +} +